{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "01_pytorch_workflow_video.ipynb",
      "provenance": [],
      "collapsed_sections": [],
      "authorship_tag": "ABX9TyPiZqHPF/YamI5YlikNi4KW",
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    },
    "accelerator": "GPU"
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/mrdbourke/pytorch-deep-learning/blob/main/video_notebooks/01_pytorch_workflow_video.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "# PyTorch Workflow\n",
        "\n",
        "Let's explore a an example PyTorch end-to-end workflow.\n",
        "\n",
        "Resources:\n",
        "* Ground truth notebook - https://github.com/mrdbourke/pytorch-deep-learning/blob/main/01_pytorch_workflow.ipynb\n",
        "* Book version of notebook - https://www.learnpytorch.io/01_pytorch_workflow/\n",
        "* Ask a question - https://github.com/mrdbourke/pytorch-deep-learning/discussions"
      ],
      "metadata": {
        "id": "aeG__6p8FZHC"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "what_were_covering = {1: \"data (prepare and load)\",\n",
        "                      2: \"build model\",\n",
        "                      3: \"fitting the model to data (training)\",\n",
        "                      4: \"making predictions and evaluting a model (inference)\",\n",
        "                      5: \"saving and loading a model\",\n",
        "                      6: \"putting it all together\"}\n",
        "\n",
        "what_were_covering"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "z_n_NlLzFwEN",
        "outputId": "0f9c66d7-e8af-4020-d53c-17c2e1ede55f"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{1: 'data (prepare and load)',\n",
              " 2: 'build model',\n",
              " 3: 'fitting the model to data (training)',\n",
              " 4: 'making predictions and evaluting a model (inference)',\n",
              " 5: 'saving and loading a model',\n",
              " 6: 'putting it all together'}"
            ]
          },
          "metadata": {},
          "execution_count": 1
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "import torch\n",
        "from torch import nn # nn contains all of PyTorch's building blocks for neural networks \n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "# Check PyTorch version\n",
        "torch.__version__"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 35
        },
        "id": "OJN3I__OGWOe",
        "outputId": "1e270c8b-bbb2-4901-b1c7-bcf3e0f1e9f5"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            },
            "text/plain": [
              "'1.10.0+cu111'"
            ]
          },
          "metadata": {},
          "execution_count": 2
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 1. Data (preparing and loading)\n",
        "\n",
        "Data can be almost anything... in machine learning.\n",
        "\n",
        "* Excel speadsheet\n",
        "* Images of any kind\n",
        "* Videos (YouTube has lots of data...)\n",
        "* Audio like songs or podcasts\n",
        "* DNA \n",
        "* Text\n",
        "\n",
        "Machine learning is a game of two parts: \n",
        "1. Get data into a numerical representation.\n",
        "2. Build a model to learn patterns in that numerical representation.\n",
        "\n",
        "To showcase this, let's create some *known* data using the linear regression formula.\n",
        "\n",
        "We'll use a linear regression formula to make a straight line with *known* **parameters**. "
      ],
      "metadata": {
        "id": "bg1IBDfEG207"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Create *known* parameters\n",
        "weight = 0.7\n",
        "bias = 0.3\n",
        "\n",
        "# Create\n",
        "start = 0\n",
        "end = 1\n",
        "step = 0.02\n",
        "X = torch.arange(start, end, step).unsqueeze(dim=1)\n",
        "y = weight * X + bias \n",
        "\n",
        "X[:10], y[:10]"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "5hCumNpHHCTU",
        "outputId": "ca51def4-8b84-4b2a-80f8-2da542907aed"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "(tensor([[0.0000],\n",
              "         [0.0200],\n",
              "         [0.0400],\n",
              "         [0.0600],\n",
              "         [0.0800],\n",
              "         [0.1000],\n",
              "         [0.1200],\n",
              "         [0.1400],\n",
              "         [0.1600],\n",
              "         [0.1800]]), tensor([[0.3000],\n",
              "         [0.3140],\n",
              "         [0.3280],\n",
              "         [0.3420],\n",
              "         [0.3560],\n",
              "         [0.3700],\n",
              "         [0.3840],\n",
              "         [0.3980],\n",
              "         [0.4120],\n",
              "         [0.4260]]))"
            ]
          },
          "metadata": {},
          "execution_count": 3
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "len(X), len(y)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "lrPJV_XkJgRT",
        "outputId": "a7c152d0-59d6-455f-c61d-4445f1311014"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "(50, 50)"
            ]
          },
          "metadata": {},
          "execution_count": 4
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Splitting data into training and test sets (one of the most important concepts in machine learning in general)\n",
        "\n",
        "Let's create a training and test set with our data."
      ],
      "metadata": {
        "id": "5GTAEnvLJlq6"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Create a train/test split\n",
        "train_split = int(0.8 * len(X))\n",
        "X_train, y_train = X[:train_split], y[:train_split]\n",
        "X_test, y_test = X[train_split:], y[train_split:] \n",
        "\n",
        "len(X_train), len(y_train), len(X_test), len(y_test)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "vpMm7mp_KtNH",
        "outputId": "ff199d0c-6974-47f7-8ba7-e51b7df4c6b6"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "(40, 40, 10, 10)"
            ]
          },
          "metadata": {},
          "execution_count": 5
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "How might we better visualize our data?\n",
        "\n",
        "This is where the data explorer's motto comes in!\n",
        "\n",
        "\"Visualize, visualize, visualize!\""
      ],
      "metadata": {
        "id": "AqArrYcENbhp"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "def plot_predictions(train_data=X_train,\n",
        "                     train_labels=y_train,\n",
        "                     test_data=X_test,\n",
        "                     test_labels=y_test,\n",
        "                     predictions=None):\n",
        "  \"\"\"\n",
        "  Plots training data, test data and compares predictions.\n",
        "  \"\"\"\n",
        "  plt.figure(figsize=(10, 7))\n",
        "\n",
        "  # Plot training data in blue\n",
        "  plt.scatter(train_data, train_labels, c=\"b\", s=4, label=\"Training data\")\n",
        "\n",
        "  # Plot test data in green\n",
        "  plt.scatter(test_data, test_labels, c=\"g\", s=4, label=\"Testing data\")\n",
        "\n",
        "  # Are there predictions?\n",
        "  if predictions is not None:\n",
        "    # Plot the predictions if they exist\n",
        "    plt.scatter(test_data, predictions, c=\"r\", s=4, label=\"Predictions\")\n",
        "  \n",
        "  # Show the legend\n",
        "  plt.legend(prop={\"size\": 14});"
      ],
      "metadata": {
        "id": "Bgb1fH7FL0O8"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "plot_predictions();"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 428
        },
        "id": "8yWmPL7gMfPE",
        "outputId": "d34d9daa-4ffe-4f22-8ee0-44fa31117490"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlMAAAGbCAYAAADgEhWsAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nO3deXRVhd3u8eeXhCEyxNgE1IBAEQdEVIgo69aCQ+sASr3evgKtQrUaF+R95a1jtUVB7W0Va/Ua22BrsWoVpdhS4IrWQh0qkoCFawBtRCpgSgJtUbQakvzuHydNk5jknLDPfL6ftbKSPZyzf2QzPOyzzxNzdwEAAODgZCV6AAAAgFRGmAIAAAiAMAUAABAAYQoAACAAwhQAAEAAOYk6cEFBgQ8dOjRRhwcAAIjY+vXr97h7YUfbEhamhg4dqsrKykQdHgAAIGJm9pfOtvEyHwAAQACEKQAAgAAIUwAAAAEQpgAAAAIgTAEAAAQQ9t18ZvaIpMmSat19VAfbTdL9ki6Q9LGkme6+IehgH3zwgWpra3XgwIGgT4U016NHDw0YMED9+/dP9CgAgAwUSTXCIkkPSvpFJ9vPlzSi+eM0ST9u/nzQPvjgA+3evVtFRUXKzc1VKK8Bn+Xu+uc//6ldu3ZJEoEKABB3YV/mc/eXJP2ti12mSPqFh6yVdKiZHRFkqNraWhUVFemQQw4hSKFLZqZDDjlERUVFqq2tTfQ4AIAMFI17pook7Wi1vLN53UE7cOCAcnNzAw2FzJKbm8tLwgCAhIjrDehmdrWZVZpZZV1dXbh94zQV0gG/XwAAiRKNMLVL0uBWy4Oa132Guy9092J3Ly4s7PDH2wAAAKSUaISpZZIut5DTJe1z95ooPC8AAEDSCxumzOxJSa9JOtbMdprZlWZ2jZld07zLSknbJFVLeljSrJhNm4FmzpypyZMnd+sxEydOVGlpaYwm6lppaakmTpyYkGMDAJAIYasR3H1amO0uaXbUJkpR4e7ZmTFjhhYtWtTt573//vsV+hZHbunSperRo0e3j5UI27dv17Bhw1RRUaHi4uJEjwMAQLdF0jOFCNTU/PuVzeXLl+uqq65qs679uxMPHDgQUeDJy8vr9iyHHXZYtx8DAAAODj9OJkoOP/zwlo9DDz20zbpPPvlEhx56qJ588kmdddZZys3NVXl5ufbu3atp06Zp0KBBys3N1QknnKCf//znbZ63/ct8EydO1KxZs3TLLbeooKBAAwYM0PXXX6+mpqY2+7R+mW/o0KG68847VVJSov79+2vQoEG655572hzn7bff1oQJE9S7d28de+yxWrlypfr27dvl1bTGxkZdf/31ys/PV35+vubMmaPGxsY2+zz33HM644wzlJ+fr8MOO0znnnuutmzZ0rJ92LBhkqRTTz1VZtbyEmFFRYW+/OUvq6CgQP3799cXvvAFvfbaaxGcCQBAJpm9YrZy5udo9orEvUhGmIqjb3/725o1a5Y2b96sr3zlK/rkk080ZswYLV++XFVVVbr22mtVUlKiF198scvneeKJJ5STk6M//vGPevDBB/WjH/1Iixcv7vIx9913n0488URt2LBBN910k2688caWcNLU1KSLL75YOTk5Wrt2rRYtWqR58+bp008/7fI57733Xj388MMqLy/Xa6+9psbGRj3xxBNt9vnoo480Z84crVu3TmvWrFFeXp4uvPBC1dfXS5LWrVsnKRS6ampqtHTpUknShx9+qMsuu0wvv/yy1q1bp5NPPlkXXHCB9u7d2+VMAIDMUr6+XI3eqPL15Ykbwt0T8jF27FjvzObNmzvd1l2zZrlnZ4c+x8szzzzjoW9tyLvvvuuSfMGCBWEfe+mll/qVV17ZsjxjxgyfNGlSy/KECRP89NNPb/OYc845p81jJkyY4LNnz25ZHjJkiE+dOrXNY44++mi/44473N39ueee8+zsbN+5c2fL9ldffdUl+c9//vNOZz3iiCP8zjvvbFlubGz0ESNG+IQJEzp9zP79+z0rK8tffvlld//396aioqLTx7i7NzU1+eGHH+6PPfZYp/tE8/cNACA1zFo+y7PnZfus5bH9h15SpXeSadL+ylR5udTYGPqcaO1vsG5sbNRdd92l0aNH63Of+5z69u2rpUuX6r333uvyeUaPHt1m+cgjjwz7o1S6eszWrVt15JFHqqjo38X1p556qrKyOv/tsW/fPtXU1Gj8+PEt67KysnTaaW1/LOM777yj6dOna/jw4erfv78GDhyopqamsL/G2tpalZSU6JhjjlFeXp769eun2trasI8DAGSWskllapjboLJJZQmbIe1vQC8pCQWpkpJETyL16dOnzfKCBQt077336v7779eJJ56ovn376pZbbgkbjNrfuG5mbe6ZitZjomHy5MkaNGiQysvLVVRUpJycHI0cObLlZb7OzJgxQ7t379Z9992noUOHqlevXjr77LPDPg4AgHhL+zBVVhb6SEavvPKKLrzwQl122WWSQi+5vv322y03sMfLcccdp/fff1/vv/++jjzySElSZWVll2ErLy9PRxxxhNauXauzzjpLUmj+devW6YgjQj/neu/evdq6daseeughnXnmmZKkDRs2qKGhoeV5evbsKUmfuXH9lVde0QMPPKBJkyZJknbv3t3m3ZEAACSLtH+ZL5kdc8wxevHFF/XKK69o69atKi0t1bvvvhv3Ob70pS/p2GOP1YwZM7Rx40atXbtW3/rWt5STk9Nlf9a1116ru+++W0uWLNFbb72lOXPmtAk8+fn5Kigo0MMPP6zq6mr94Q9/0DXXXKOcnH9n+AEDBig3N1erVq3S7t27tW/fPkmh783jjz+uzZs3q6KiQlOnTm0JXgAAJBPCVAJ95zvf0bhx43T++efri1/8ovr06aOvfe1rcZ8jKytLzz77rD799FONGzdOM2bM0K233iozU+/evTt93HXXXadvfOMb+uY3v6nTTjtNTU1NbebPysrS4sWLtWnTJo0aNUqzZ8/WHXfcoV69erXsk5OTowceeEA//elPdeSRR2rKlCmSpEceeUT79+/X2LFjNXXqVF1xxRUaOnRozL4HAIDkkQx1B91h3s127WgpLi72ysrKDrdt2bJFxx9/fJwnQmsbN27UySefrMrKSo0dOzbR40SE3zcAkB5y5ueo0RuVbdlqmNsQ/gFxYGbr3b3DH9XBlSlIkp599lk9//zzevfdd7V69WrNnDlTJ510ksaMGZPo0QAAGaZkbImyLVslY5Pg3WMRSPsb0BGZDz/8UDfddJN27Nih/Px8TZw4Uffdd1/YnzkIAEC0lU0qS2jVQXcRpiBJuvzyy3X55ZcnegwAAFIOL/MBAAAEQJgCAAAIgDAFAADiItUqDyJFmAIAAHFRvr5cjd6o8vVJ8ANzo4gwBQAA4iLVKg8ixbv5AABAXKRa5UGkuDKVwoYOHaoFCxYk5NiTJ0/WzJkzE3JsAACSCWEqSsysy48gweP222/XqFGjPrO+oqJCs2bNCjB1/KxZs0Zmpj179iR6FAAAooqX+aKkpqam5evly5frqquuarMuNzc36scsLCyM+nMCAIDu4cpUlBx++OEtH4ceeuhn1r300ksaO3asevfurWHDhunWW29VfX19y+OXLl2q0aNHKzc3V4cddpgmTJig3bt3a9GiRZo3b56qqqparnItWrRI0mdf5jMzLVy4UF/96lfVp08fff7zn9fjjz/eZs7XX39dY8aMUe/evXXKKado5cqVMjOtWbOm01/bxx9/rJkzZ6pv374aOHCgvve9731mn8cff1ynnnqq+vXrpwEDBuirX/2qdu3aJUnavn27zjzzTEmhANj6St1zzz2nM844Q/n5+TrssMN07rnnasuWLd3+/gMAEiddKw8iRZiKg1WrVulrX/uaSktLVVVVpUceeURLlizRLbfcIkn661//qqlTp2rGjBnasmWLXnrpJV122WWSpEsvvVTXXXedjj32WNXU1KimpkaXXnppp8eaP3++pkyZoo0bN+rSSy/VFVdcoffee0+StH//fk2ePFnHHXec1q9fr7vvvls33HBD2Pmvv/56vfDCC/rVr36lF198UW+88YZeeumlNvvU19dr3rx52rhxo5YvX649e/Zo2rRpkqTBgwfrV7/6lSSpqqpKNTU1uv/++yVJH330kebMmaN169ZpzZo1ysvL04UXXtgmaAIAklu6Vh5EzN0T8jF27FjvzObNmzvd1l2zls/y7HnZPmv5rKg9ZzjPPPOMh761IWeccYbPnz+/zT7PPvus9+nTx5uamnz9+vUuybdv397h8912221+wgknfGb9kCFD/J577mlZluQ333xzy/KBAwc8NzfXH3vsMXd3/8lPfuL5+fn+8ccft+zzxBNPuCRfvXp1h8f+8MMPvWfPnv7444+3WZeXl+czZszo9HuwZcsWl+Q7duxwd/fVq1e7JK+rq+v0Me7u+/fv96ysLH/55Ze73K8j0fx9AwCIXCL+rY03SZXeSaZJ+ytTyZCW169fr7vuukt9+/Zt+Zg+fbo++ugj/fWvf9VJJ52kc845R6NGjdIll1yiH//4x6qrqzuoY40ePbrl65ycHBUWFqq2tlaStHXrVo0aNarN/VunnXZal8/3zjvvqL6+XuPHj29Z17dvX5144olt9tuwYYOmTJmiIUOGqF+/fiouLpaklqtiXT3/9OnTNXz4cPXv318DBw5UU1NT2McBAJJH2aQyNcxtSMvag0ikfZhKhoKwpqYm3XbbbfrTn/7U8rFp0yb9+c9/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\n",
            "text/plain": [
              "<Figure size 720x504 with 1 Axes>"
            ]
          },
          "metadata": {
            "needs_background": "light"
          }
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 2. Build model \n",
        "\n",
        "Our first PyTorch model!\n",
        "\n",
        "This is very exciting... let's do it!\n",
        "\n",
        "Because we're going to be building classes throughout the course, I'd recommend getting familiar with OOP in Python, to do so you can use the following resource from Real Python: https://realpython.com/python3-object-oriented-programming/\n",
        "\n",
        "What our model does:\n",
        "* Start with random values (weight & bias)\n",
        "* Look at training data and adjust the random values to better represent (or get closer to) the ideal values (the weight & bias values we used to create the data)\n",
        "\n",
        "How does it do so?\n",
        "\n",
        "Through two main algorithms:\n",
        "1. Gradient descent - https://youtu.be/IHZwWFHWa-w\n",
        "2. Backpropagation - https://youtu.be/Ilg3gGewQ5U"
      ],
      "metadata": {
        "id": "0z7q5QhLOv_q"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from torch import nn\n",
        "\n",
        "# Create linear regression model class\n",
        "class LinearRegressionModel(nn.Module): # <- almost everything in PyTorch inherhits from nn.Module\n",
        "  def __init__(self):\n",
        "    super().__init__()\n",
        "    self.weights = nn.Parameter(torch.randn(1, # <- start with a random weight and try to adjust it to the ideal weight\n",
        "                                            requires_grad=True, # <- can this parameter be updated via gradient descent?\n",
        "                                            dtype=torch.float)) # <- PyTorch loves the datatype torch.float32\n",
        "    \n",
        "    self.bias = nn.Parameter(torch.randn(1, # <- start with a random bias and try to adjust it to the ideal bias\n",
        "                                         requires_grad=True, # <- can this parameter be updated via gradient descent?\n",
        "                                         dtype=torch.float)) # <- PyTorch loves the datatype torch.float32 \n",
        "    \n",
        "  # Forward method to define the computation in the model\n",
        "  def forward(self, x: torch.Tensor) -> torch.Tensor: # <- \"x\" is the input data\n",
        "    return self.weights * x + self.bias # this is the linear regression formula"
      ],
      "metadata": {
        "id": "qirhP4VUkOky"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "### PyTorch model building essentials\n",
        "\n",
        "* torch.nn - contains all of the buildings for computational graphs (a neural network can be considered a computational graph)\n",
        "* torch.nn.Parameter - what parameters should our model try and learn, often a PyTorch layer from torch.nn will set these for us \n",
        "* torch.nn.Module - The base class for all neural network modules, if you subclass it, you should overwrite forward()\n",
        "* torch.optim - this where the optimizers in PyTorch live, they will help with gradient descent\n",
        "* def forward() - All nn.Module subclasses require you to overwrite forward(), this method defines what happens in the forward computation \n",
        "\n",
        "See more of these essential modules via the PyTorch cheatsheet - https://pytorch.org/tutorials/beginner/ptcheat.html "
      ],
      "metadata": {
        "id": "JH_vD6ICnRUO"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Checking the contents of our PyTorch model\n",
        "\n",
        "Now we've created a model, let's see what's inside...\n",
        "\n",
        "So we can check our model parameters or what's inside our model using `.parameters()`."
      ],
      "metadata": {
        "id": "S_8fP6B0rYnN"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Create a random seed\n",
        "torch.manual_seed(42)\n",
        "\n",
        "# Create an instance of the model (this is a subclass of nn.Module)\n",
        "model_0 = LinearRegressionModel()\n",
        "\n",
        "# Check out the parameters\n",
        "list(model_0.parameters())"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "0737rQGNtDxP",
        "outputId": "2a477df1-d234-4db1-c25d-f6eb734cbf86"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[Parameter containing:\n",
              " tensor([0.3367], requires_grad=True), Parameter containing:\n",
              " tensor([0.1288], requires_grad=True)]"
            ]
          },
          "metadata": {},
          "execution_count": 9
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# List named parameters\n",
        "model_0.state_dict()"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "hdzvifGftWYZ",
        "outputId": "983cdf2a-c582-4fbf-e8d8-9060bb32bb30"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "OrderedDict([('weights', tensor([0.3367])), ('bias', tensor([0.1288]))])"
            ]
          },
          "metadata": {},
          "execution_count": 10
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Making prediction using `torch.inference_mode()`\n",
        "\n",
        "To check our model's predictive power, let's see how well it predicts `y_test` based on `X_test`.\n",
        "\n",
        "When we pass data through our model, it's going to run it through the `forward()` method."
      ],
      "metadata": {
        "id": "XlAsG4S-uJO5"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "y_preds = model_0(X_test)\n",
        "y_preds"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "j_nRrqGMwm0N",
        "outputId": "f9cbe561-5994-4f99-e4e2-96f70b06fbb8"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "tensor([[0.3982],\n",
              "        [0.4049],\n",
              "        [0.4116],\n",
              "        [0.4184],\n",
              "        [0.4251],\n",
              "        [0.4318],\n",
              "        [0.4386],\n",
              "        [0.4453],\n",
              "        [0.4520],\n",
              "        [0.4588]], grad_fn=<AddBackward0>)"
            ]
          },
          "metadata": {},
          "execution_count": 11
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Make predictions with model\n",
        "with torch.inference_mode():\n",
        "  y_preds = model_0(X_test)\n",
        "  \n",
        "\n",
        "# # You can also do something similar with torch.no_grad(), however, torch.inference_mode() is preferred\n",
        "# with torch.no_grad():\n",
        "#   y_preds = model_0(X_test)\n",
        "\n",
        "y_preds"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "qCASe_bouVKL",
        "outputId": "cdcc4351-3173-44d2-b854-7bdecedcd0b1"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "tensor([[0.3982],\n",
              "        [0.4049],\n",
              "        [0.4116],\n",
              "        [0.4184],\n",
              "        [0.4251],\n",
              "        [0.4318],\n",
              "        [0.4386],\n",
              "        [0.4453],\n",
              "        [0.4520],\n",
              "        [0.4588]])"
            ]
          },
          "metadata": {},
          "execution_count": 12
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "See more on inference mode here - https://twitter.com/PyTorch/status/1437838231505096708?s=20&t=cnKavO9iTgwQ-rfri6u7PQ "
      ],
      "metadata": {
        "id": "HYHvIyDsxL65"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "y_test"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "FVREWa_BvzI0",
        "outputId": "a89181a5-a12c-4f52-ef03-e01943da9561"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "tensor([[0.8600],\n",
              "        [0.8740],\n",
              "        [0.8880],\n",
              "        [0.9020],\n",
              "        [0.9160],\n",
              "        [0.9300],\n",
              "        [0.9440],\n",
              "        [0.9580],\n",
              "        [0.9720],\n",
              "        [0.9860]])"
            ]
          },
          "metadata": {},
          "execution_count": 13
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "plot_predictions(predictions=y_preds)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 428
        },
        "id": "331WoqFewSnl",
        "outputId": "059f3ceb-bc54-42b4-88c5-433930e8a50d"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlMAAAGbCAYAAADgEhWsAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nO3deXxV9Z3/8feHhCWyiRJAFgERFQRUiChtFVQcF0DGcSyLtTBajQ9gqr8RlWrLpta2Yhk7RifaKtZdER0GKWoZUHREkoAwslkEFTBCcDouUIUkn98fN02TkOTecO5+X8/H4z6Sc873nvNJTgjvfM+5n2vuLgAAAByZZokuAAAAIJURpgAAAAIgTAEAAARAmAIAAAiAMAUAABBAdqIO3LFjR+/Vq1eiDg8AABCxkpKSfe6eW9+2hIWpXr16qbi4OFGHBwAAiJiZfdzQNi7zAQAABECYAgAACIAwBQAAEABhCgAAIADCFAAAQABhX81nZo9KGi1pr7sPqGe7Sbpf0qWSDkia7O5rgxb25Zdfau/evTp06FDQXSHNNW/eXJ06dVK7du0SXQoAIANF0hphgaQHJP2+ge2XSOpb9ThL0kNVH4/Yl19+qT179qhbt27KyclRKK8Bh3N3/eUvf9Hu3bsliUAFAIi7sJf53P1NSf/byJCxkn7vIaslHW1mxwUpau/everWrZuOOuooghQaZWY66qij1K1bN+3duzfR5QAAMlA07pnqJmlnjeVdVeuO2KFDh5STkxOoKGSWnJwcLgkDABIirjegm9n1ZlZsZsVlZWXhxsapKqQDfl4AAIkSjTC1W1KPGsvdq9Ydxt0fdvc8d8/Lza337W0AAABSSjTC1GJJP7SQsyV94e6lUdgvAABA0gsbpszsGUnvSDrZzHaZ2bVmdoOZ3VA1ZKmk7ZK2SXpE0pSYVZuBJk+erNGjRzfpOSNGjNC0adNiVFHjpk2bphEjRiTk2AAAJELY1gjuPiHMdpc0NWoVpahw9+xMmjRJCxYsaPJ+77//foW+xZFbtGiRmjdv3uRjJcJHH32k3r17q6ioSHl5eYkuBwCAJoukzxQiUFr6tyubS5Ys0XXXXVdrXd1XJx46dCiiwNO+ffsm13LMMcc0+TkAAODI8HYyUdKlS5fqx9FHH11r3TfffKOjjz5azzzzjM4//3zl5OSosLBQn3/+uSZMmKDu3bsrJydHp556qh577LFa+617mW/EiBGaMmWKbr/9dnXs2FGdOnXS9OnTVVlZWWtMzct8vXr10l133aX8/Hy1a9dO3bt317333lvrOB988IGGDx+uVq1a6eSTT9bSpUvVpk2bRmfTKioqNH36dHXo0EEdOnTQTTfdpIqKilpjli1bpnPOOUcdOnTQMccco4suukibN2+u3t67d29J0plnnikzq75EWFRUpL/7u79Tx44d1a5dO33ve9/TO++8E8GZAABkkqmvTFX23GxNfSVxF8kIU3H0k5/8RFOmTNGmTZv093//9/rmm280ePBgLVmyRBs3btSNN96o/Px8LV++vNH9PPXUU8rOztZ///d/64EHHtC//uu/6rnnnmv0OfPnz9fAgQO1du1a3Xbbbbr11lurw0llZaUuv/xyZWdna/Xq1VqwYIHmzJmjb7/9ttF93nfffXrkkUdUWFiod955RxUVFXrqqadqjdm/f79uuukmrVmzRitXrlT79u01ZswYHTx4UJK0Zs0aSaHQVVpaqkWLFkmSvvrqK1199dVatWqV1qxZo9NPP12XXnqpPv/880ZrAgBklsKSQlV4hQpLChNXhLsn5DFkyBBvyKZNmxrc1lRTprhnZYU+xssLL7zgoW9tyI4dO1ySz5s3L+xzx40b59dee2318qRJk3zUqFHVy8OHD/ezzz671nNGjhxZ6znDhw/3qVOnVi/37NnTx48fX+s5J554ot95553u7r5s2TLPysryXbt2VW9/++23XZI/9thjDdZ63HHH+V133VW9XFFR4X379vXhw4c3+Jyvv/7amzVr5qtWrXL3v31vioqKGnyOu3tlZaV36dLFn3jiiQbHRPPnBgCQGqYsmeJZc7J8ypLY/kcvqdgbyDRpPzNVWChVVIQ+JlrdG6wrKip09913a9CgQTr22GPVpk0bLVq0SJ988kmj+xk0aFCt5a5du4Z9K5XGnrNlyxZ17dpV3br9rXH9mWeeqWbNGv7x+OKLL1RaWqphw4ZVr2vWrJnOOqv22zJ++OGHmjhxovr06aN27dqpc+fOqqysDPs17t27V/n5+TrppJPUvn17tW3bVnv37g37PABAZikYVaDymeUqGFWQsBrS/gb0/PxQkMrPT3QlUuvWrWstz5s3T/fdd5/uv/9+DRw4UG3atNHtt98e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lZWl1157Ta+99poGDRqk3/3ud+rbt6/Wr1/f5GM1b9681rKZqbKyMlpfSr3279+viy66SEcddZSeeOIJFRUVadmyZZIU9nLd6NGjVVZWpsLCQr377rtat26dsrOzucwHAEgZaf9qvmRoEDZ48GBt2bJFJ554YoNjzEzDhg3TsGHDNHPmTJ166ql67rnndNppp6lFixaqqKgIXMcpp5yixx9/XH/5y1+qZ6fWrFnT6HP69Omj5s2ba/Xq1TrhhBMkhcLT+++/rz59+kgKzXjt27dPP//5z9W7d29JoRvqa2rRooUk1fo6Pv/8c23ZskUPPvhg9Q3qa9euVXl5eeCvFQCAeEn7malkMHPmTD399NOaOXOm3n//fW3ZskULFy7UrbfeKklavXq17rrrLhUVFemTTz7R4sWLtXPnTvXv319S6FV7H3/8sdauXat9+/bp22+/PaI6Jk6cqKysLF133XXatGmT/vjHP1a/Ms/M6n1OmzZtdO211+q2227T66+/ro0bN+qaa66pFYqOP/54tWzZUg888IC2b9+uV155RT/72c9q7adnz54yM73yyisqKyvT119/rQ4dOqhjx4565JFHtG3bNr3xxhu64YYblJ2d9hkfAJBGCFNxcNFFF+mVV17RihUrNHToUA0dOlS/+MUvdPzxx0uS2rdvr7ffflujR49W3759dfPNN+tnP/uZfvCDH0iSrrjiCl166aW64IILlJubq2eeeeaI6mjbtq3+8z//Uxs3btQZZ5yhW265RbNnz5YktWrVqsHnzZs3T+edd54uv/xynXfeeRowYIDOPffc6u25ubl6/PHH9fLLL6t///6aM2eOfv3rX9faR7du3TRnzhzdcccd6ty5s6ZNm6ZmzZrpueee04YNGzRgwABNnTpVd955p1q2bHlEXx8AIHoyvd1BU1joBvX4y8vL8+Li4nq3bd68Wf369YtzRZnpP/7jP3T55Zdr79696tixY6LLCYSfGwCInuy52arwCmVZlspncvuFmZW4e15925iZyjCPP/64Vq1apY8++khLlizRTTfdpDFjxqR8kAIARFcyvIArVXBzSobZs2ePZs2apdLSUnXp0kWjRo3SL3/5y0SXBQBIMsnwAq5UQZjKMLfeemv1je8AACA4LvMBAAAEQJgCAAAIgDAFAEAGoeVB9BGmAADIIMnwnrXphjAFAEAGoeVB9PFqPgAAMggtD6KPmakUtHDhwlrvpbdgwQK1adMm0D5XrlwpM9O+ffuClgcAQEYhTEXR5MmTZWYyMzVv3lwnnHCCpk+frv3798f0uOPGjdP27dsjHt+rVy/Nmzev1rrvfOc7Ki0t1bHHHhvt8gAASGsRhSkzu9jMtprZNjObUc/2nma23Mw2mNlKM+se/VJTw8iRI1VaWqrt27frrrvu0oMPPqjp06cfNq68vFzRel/EnJwcderUKdA+WrRooS5dutSa8QIAAOGFDVNmliWpQNIlkvpLmmBm/esMmyfp9+4+SNJcSfdEu9BU0bJlS3Xp0kU9evTQxIkTddVVV+nll1/W7NmzNWDAAC1YsEB9+vRRy5YttX//fn3xxRe6/vrr1alTJ7Vt21bDhw9X3TeA/v3vf6+ePXvqqKOO0ujRo7Vnz55a2+u7zLd06VKdddZZysnJ0bHHHqsxY8bom2++0YgRI/Txxx/rlltuqZ5Fk+q/zLdo0SINHDhQLVu2VI8ePXT33XfXCoC9evXSXXfdpfz8fLVr107du3fXvffeW6uOwsJCnXTSSWrVqpU6duyoiy66SOXlvGEmAEQbLQ8SJ5KZqaGStrn7dnc/KOlZSWPrjOkv6b+qPl9Rz/aMlZOTo0OHDkmSduzYoaefflovvPCC1q9fr5YtW2rUqFHavXu3lixZonXr1uncc8/V+eefr9LSUknSu+++q8mTJ+v666/Xe++9pzFjxmjmzJmNHnPZsmW67LLLdOGFF6qkpEQrVqzQ8OHDVVlZqUWLFql79+6aOXOmSktLq49TV0lJia688kr9wz/8g/7nf/5Hv/jFL3TPPffogQceqDVu/vz5GjhwoNauXavbbrtNt956q9555x1JUnFxsaZOnapZs2Zp69atWr58uS6++OKg31IAQD1oeZBA7t7oQ9I/SvptjeWrJT1QZ8zTkm6s+vwfJLmkY+vZ1/WSiiUVH3/88d6QTZs2NbityaZMcc/KCn2MsUmTJvmoUaOql999910/9thj/fvf/77PmjXLs7Oz/bPPPqvevnz5cm/durUfOHCg1n5OO+00/+Uvf+nu7hMmTPCRI0fW2n7ttdd66NSFPPbYY966devq5e985zs+bty4Buvs2bOn33vvvbXWrVixwiV5WVmZu7tPnDjRzzvvvFpjZs2a5d26dau1n/Hjx9cac+KJJ/qdd97p7u4vvviit2vXzr/88ssGa4mmqP7cAECKmbJkimfNyfIpS2L//10mklTsDWSlaN2APl3ScDNbJ2m4pN2SKuoJbg+7e5675+Xm5kbp0GEUFkoVFaGPcbBs2TK1adNGrVq10rBhw3Tuuefq3/7t3yRJ3bt3V+fOnavHlpSU6MCBA8rNzVWbNm2qH++//74+/PBDSdLmzZs1bNiwWseou1zXunXrdMEFFwT6OjZv3qzvfve7tdZ973vf0+7du/Xll19Wrxs0aFCtMV27dtXevXslSRdeeKF69uyp3r1766qrrtLjjz+ur776KlBdAID6FYwqUPnMctoeJEAkfaZ2S+pRY7l71bpq7v6pQjNSMrM2kq5w9/+LVpGB5OeHglR+fJqTnXvuuXr44YfVvHlzde3aVc2bN6/e1rp161pjKysr1blzZ61ateqw/bRr1y7mtR6pmjep1/z6/rqtsrJSktS2bVutXbtWb775pl5//XXdc889uv3221VUVKSuXbvGtWYAAGIlkpmpIkl9zay3mbWQNF7S4poDzKyjmf11Xz+R9Gh0ywygoEAqLw99jIOjjjpKJ554onr27HlY0Khr8ODB2rNnj5o1a6YTTzyx1uOvr87r16+fVq9eXet5dZfrOuOMM7R8+fIGt7do0UIVFYdNHNbSr18/vf3227XWvfXWW+revbvatm3b6HNrys7O1vnnn6977rlHGzZs0P79+7VkyZKInw8AQLILG6bcvVzSNEmvStos6Xl332hmc83ssqphIyRtNbMPJHWWdHeM6k0rI0eO1He/+12NHTtWf/jDH7Rjxw698847mjVrVvVs1Y9//GP98Y9/1D333KM//elPeuSRR/TSSy81ut877rhDL7zwgn76059q06ZN2rhxo+bPn68DBw5ICr0Kb9WqVdq9e3eDTTpvvvlmvfHGG5o9e7Y++OADPfXUU7rvvvt06623Rvz1LVmyRPfff7/WrVunjz/+WE8//bS++uor9evXL+J9AACQ7CK6Z8rdl7r7Se7ex93vrlo3090XV32+0N37Vo35kbt/G8ui04WZaenSpTr//PN13XXX6eSTT9b3v/99bd26tfoy2Nlnn63f/e53euihhzRo0CAtWrRIs2fPbnS/l156qV566SX94Q9/0BlnnKHhw4drxYoVatYsdLrnzp2rnTt3qk+fPmro3rXB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\n",
            "text/plain": [
              "<Figure size 720x504 with 1 Axes>"
            ]
          },
          "metadata": {
            "needs_background": "light"
          }
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 3. Train model\n",
        "\n",
        "The whole idea of training is for a model to move from some *unknown* parameters (these may be random) to some *known* parameters.\n",
        "\n",
        "Or in other words from a poor representation of the data to a better representation of the data.\n",
        "\n",
        "One way to measure how poor or how wrong your models predictions are is to use a loss function.\n",
        "\n",
        "* Note: Loss function may also be called cost function or criterion in different areas. For our case, we're going to refer to it as a loss function.\n",
        "\n",
        "Things we need to train:\n",
        "\n",
        "* **Loss function:** A function to measure how wrong your model's predictions are to the ideal outputs, lower is better.\n",
        "* **Optimizer:** Takes into account the loss of a model and adjusts the model's parameters (e.g. weight & bias in our case) to improve the loss function - https://pytorch.org/docs/stable/optim.html#module-torch.optim\n",
        "  * Inside the optimizer you'll often have to set two parameters:\n",
        "    * `params` - the model parameters you'd like to optimize, for example `params=model_0.parameters()`\n",
        "    * `lr` (learning rate) - the learning rate is a hyperparameter that defines how big/small the optimizer changes the parameters with each step (a small `lr` results in small changes, a large `lr` results in large changes)\n",
        "\n",
        "And specifically for PyTorch, we need:\n",
        "* A training loop\n",
        "* A testing loop"
      ],
      "metadata": {
        "id": "FC7cHOnqwWi8"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "list(model_0.parameters())"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "gR7Xy8FqQ1x1",
        "outputId": "85ea9234-292b-4269-c450-3a9088e3d65a"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[Parameter containing:\n",
              " tensor([0.3367], requires_grad=True), Parameter containing:\n",
              " tensor([0.1288], requires_grad=True)]"
            ]
          },
          "metadata": {},
          "execution_count": 15
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Check out our model's parameters (a parameter is a value that the model sets itself)\n",
        "model_0.state_dict()"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "MeA8r0whRzVM",
        "outputId": "3bf7b029-3a6d-4f4d-cb37-e493fff66f16"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "OrderedDict([('weights', tensor([0.3367])), ('bias', tensor([0.1288]))])"
            ]
          },
          "metadata": {},
          "execution_count": 16
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Setup a loss function\n",
        "loss_fn = nn.L1Loss()\n",
        "\n",
        "# Setup an optimizer (stochastic gradient descent)\n",
        "optimizer = torch.optim.SGD(params=model_0.parameters(), # we want to optimize the parameters present in our model\n",
        "                            lr=0.01) # lr = learning rate = possibly the most important hyperparameter you can set"
      ],
      "metadata": {
        "id": "FhpnOr3vR5jI"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": [
        "> **Q:** Which loss function and optimizer should I use?\n",
        ">\n",
        "> **A:** This will be problem specific. But with experience, you'll get an idea of what works and what doesn't with your particular problem set.\n",
        ">\n",
        "> For example, for a regression problem (like ours), a loss function of `nn.L1Loss()` and an optimizer like `torch.optim.SGD()` will suffice.\n",
        ">\n",
        "> But for a classification problem like classifying whether a photo is of a dog or a cat, you'll likely want to use a loss function of `nn.BCELoss()` (binary cross entropy loss). "
      ],
      "metadata": {
        "id": "zR0wku4LUzr7"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "### Building a training loop (and a testing loop) in PyTorch\n",
        "\n",
        "A couple of things we need in a training loop:\n",
        "0. Loop through the data and do...\n",
        "1. Forward pass (this involves data moving through our model's `forward()` functions) to make predictions on data - also called forward propagation \n",
        "2. Calculate the loss (compare forward pass predictions to ground truth labels)\n",
        "3. Optimizer zero grad\n",
        "4. Loss backward - move backwards through the network to calculate the gradients of each of the parameters of our model with respect to the loss (**backpropagation** - https://www.youtube.com/watch?v=tIeHLnjs5U8)\n",
        "5. Optimizer step - use the optimizer to adjust our model's parameters to try and improve the loss (**gradient descent** - https://youtu.be/IHZwWFHWa-w)"
      ],
      "metadata": {
        "id": "Ffr8kYJTXwkD"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "torch.manual_seed(42)\n",
        "\n",
        "# An epoch is one loop through the data... (this is a hyperparameter because we've set it ourselves)\n",
        "epochs = 200\n",
        "\n",
        "# Track different values\n",
        "epoch_count = [] \n",
        "loss_values = []\n",
        "test_loss_values = [] \n",
        "\n",
        "### Training\n",
        "# 0. Loop through the data\n",
        "for epoch in range(epochs): \n",
        "  # Set the model to training mode\n",
        "  model_0.train() # train mode in PyTorch sets all parameters that require gradients to require gradients \n",
        "\n",
        "  # 1. Forward pass\n",
        "  y_pred = model_0(X_train)\n",
        "\n",
        "  # 2. Calculate the loss\n",
        "  loss = loss_fn(y_pred, y_train)\n",
        "\n",
        "  # 3. Optimizer zero grad\n",
        "  optimizer.zero_grad() \n",
        "\n",
        "  # 4. Perform backpropagation on the loss with respect to the parameters of the model (calculate gradients of each parameter)\n",
        "  loss.backward()\n",
        "\n",
        "  # 5. Step the optimizer (perform gradient descent)\n",
        "  optimizer.step() # by default how the optimizer changes will accumulate through the loop so... we have to zero them above in step 3 for the next iteration of the loop\n",
        "\n",
        "  ### Testing\n",
        "  model_0.eval() # turns off different settings in the model not needed for evaluation/testing (dropout/batch norm layers)\n",
        "  with torch.inference_mode(): # turns off gradient tracking & a couple more things behind the scenes - https://twitter.com/PyTorch/status/1437838231505096708?s=20&t=aftDZicoiUGiklEP179x7A\n",
        "  # with torch.no_grad(): # you may also see torch.no_grad() in older PyTorch code\n",
        "    # 1. Do the forward pass \n",
        "    test_pred = model_0(X_test)\n",
        "\n",
        "    # 2. Calculate the loss\n",
        "    test_loss = loss_fn(test_pred, y_test)\n",
        "\n",
        "  # Print out what's happenin'\n",
        "  if epoch % 10 == 0:\n",
        "    epoch_count.append(epoch)\n",
        "    loss_values.append(loss)\n",
        "    test_loss_values.append(test_loss)\n",
        "    print(f\"Epoch: {epoch} | Loss: {loss} | Test loss: {test_loss}\")\n",
        "    # Print out model state_dict()\n",
        "    print(model_0.state_dict())"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "TV8WOxYPaTlP",
        "outputId": "e05110b7-2289-48c9-e99c-cfca09e0f9b3"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Epoch: 0 | Loss: 0.31288138031959534 | Test loss: 0.48106518387794495\n",
            "OrderedDict([('weights', tensor([0.3406])), ('bias', tensor([0.1388]))])\n",
            "Epoch: 10 | Loss: 0.1976713240146637 | Test loss: 0.3463551998138428\n",
            "OrderedDict([('weights', tensor([0.3796])), ('bias', tensor([0.2388]))])\n",
            "Epoch: 20 | Loss: 0.08908725529909134 | Test loss: 0.21729660034179688\n",
            "OrderedDict([('weights', tensor([0.4184])), ('bias', tensor([0.3333]))])\n",
            "Epoch: 30 | Loss: 0.053148526698350906 | Test loss: 0.14464017748832703\n",
            "OrderedDict([('weights', tensor([0.4512])), ('bias', tensor([0.3768]))])\n",
            "Epoch: 40 | Loss: 0.04543796554207802 | Test loss: 0.11360953003168106\n",
            "OrderedDict([('weights', tensor([0.4748])), ('bias', tensor([0.3868]))])\n",
            "Epoch: 50 | Loss: 0.04167863354086876 | Test loss: 0.09919948130846024\n",
            "OrderedDict([('weights', tensor([0.4938])), ('bias', tensor([0.3843]))])\n",
            "Epoch: 60 | Loss: 0.03818932920694351 | Test loss: 0.08886633068323135\n",
            "OrderedDict([('weights', tensor([0.5116])), ('bias', tensor([0.3788]))])\n",
            "Epoch: 70 | Loss: 0.03476089984178543 | Test loss: 0.0805937647819519\n",
            "OrderedDict([('weights', tensor([0.5288])), ('bias', tensor([0.3718]))])\n",
            "Epoch: 80 | Loss: 0.03132382780313492 | Test loss: 0.07232122868299484\n",
            "OrderedDict([('weights', tensor([0.5459])), ('bias', tensor([0.3648]))])\n",
            "Epoch: 90 | Loss: 0.02788739837706089 | Test loss: 0.06473556160926819\n",
            "OrderedDict([('weights', tensor([0.5629])), ('bias', tensor([0.3573]))])\n",
            "Epoch: 100 | Loss: 0.024458957836031914 | Test loss: 0.05646304413676262\n",
            "OrderedDict([('weights', tensor([0.5800])), ('bias', tensor([0.3503]))])\n",
            "Epoch: 110 | Loss: 0.021020207554101944 | Test loss: 0.04819049686193466\n",
            "OrderedDict([('weights', tensor([0.5972])), ('bias', tensor([0.3433]))])\n",
            "Epoch: 120 | Loss: 0.01758546568453312 | Test loss: 0.04060482233762741\n",
            "OrderedDict([('weights', tensor([0.6141])), ('bias', tensor([0.3358]))])\n",
            "Epoch: 130 | Loss: 0.014155393466353416 | Test loss: 0.03233227878808975\n",
            "OrderedDict([('weights', tensor([0.6313])), ('bias', tensor([0.3288]))])\n",
            "Epoch: 140 | Loss: 0.010716589167714119 | Test loss: 0.024059748277068138\n",
            "OrderedDict([('weights', tensor([0.6485])), ('bias', tensor([0.3218]))])\n",
            "Epoch: 150 | Loss: 0.0072835334576666355 | Test loss: 0.016474086791276932\n",
            "OrderedDict([('weights', tensor([0.6654])), ('bias', tensor([0.3143]))])\n",
            "Epoch: 160 | Loss: 0.0038517764769494534 | Test loss: 0.008201557211577892\n",
            "OrderedDict([('weights', tensor([0.6826])), ('bias', tensor([0.3073]))])\n",
            "Epoch: 170 | Loss: 0.008932482451200485 | Test loss: 0.005023092031478882\n",
            "OrderedDict([('weights', tensor([0.6951])), ('bias', tensor([0.2993]))])\n",
            "Epoch: 180 | Loss: 0.008932482451200485 | Test loss: 0.005023092031478882\n",
            "OrderedDict([('weights', tensor([0.6951])), ('bias', tensor([0.2993]))])\n",
            "Epoch: 190 | Loss: 0.008932482451200485 | Test loss: 0.005023092031478882\n",
            "OrderedDict([('weights', tensor([0.6951])), ('bias', tensor([0.2993]))])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "import numpy as np\n",
        "np.array(torch.tensor(loss_values).numpy()), test_loss_values"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "O6EZVQi1759Y",
        "outputId": "e1ea02bd-efe8-4655-eea3-50e88e951189"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "(array([0.31288138, 0.19767132, 0.08908726, 0.05314853, 0.04543797,\n",
              "        0.04167863, 0.03818933, 0.0347609 , 0.03132383, 0.0278874 ,\n",
              "        0.02445896, 0.02102021, 0.01758547, 0.01415539, 0.01071659,\n",
              "        0.00728353, 0.00385178, 0.00893248, 0.00893248, 0.00893248],\n",
              "       dtype=float32),\n",
              " [tensor(0.4811),\n",
              "  tensor(0.3464),\n",
              "  tensor(0.2173),\n",
              "  tensor(0.1446),\n",
              "  tensor(0.1136),\n",
              "  tensor(0.0992),\n",
              "  tensor(0.0889),\n",
              "  tensor(0.0806),\n",
              "  tensor(0.0723),\n",
              "  tensor(0.0647),\n",
              "  tensor(0.0565),\n",
              "  tensor(0.0482),\n",
              "  tensor(0.0406),\n",
              "  tensor(0.0323),\n",
              "  tensor(0.0241),\n",
              "  tensor(0.0165),\n",
              "  tensor(0.0082),\n",
              "  tensor(0.0050),\n",
              "  tensor(0.0050),\n",
              "  tensor(0.0050)])"
            ]
          },
          "metadata": {},
          "execution_count": 19
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Plot the loss curves\n",
        "plt.plot(epoch_count, np.array(torch.tensor(loss_values).numpy()), label=\"Train loss\")\n",
        "plt.plot(epoch_count, test_loss_values, label=\"Test loss\")\n",
        "plt.title(\"Training and test loss curves\")\n",
        "plt.ylabel(\"Loss\")\n",
        "plt.xlabel(\"Epochs\")\n",
        "plt.legend();"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 295
        },
        "id": "ccr-GEYe7da1",
        "outputId": "68b6980f-85ab-4811-cd53-be0d1091ff10"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "<Figure size 432x288 with 1 Axes>"
            ]
          },
          "metadata": {
            "needs_background": "light"
          }
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "with torch.inference_mode():\n",
        "  y_preds_new = model_0(X_test)"
      ],
      "metadata": {
        "id": "vfl9oAt_09Fd"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "model_0.state_dict()"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "i3Y-ilS3jCdG",
        "outputId": "4bc413d2-3b5b-4f4d-c822-5f3693f3796d"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "OrderedDict([('weights', tensor([0.6990])), ('bias', tensor([0.3093]))])"
            ]
          },
          "metadata": {},
          "execution_count": 22
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "weight, bias"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "cE8uVRUeg39p",
        "outputId": "9abe949e-3ba1-4d7e-91ee-f0dc59371641"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "(0.7, 0.3)"
            ]
          },
          "metadata": {},
          "execution_count": 23
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "plot_predictions(predictions=y_preds);"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 428
        },
        "id": "f_gboBMSl13p",
        "outputId": "3c5bce1a-ad41-44ac-bf7f-326d4b5c4308"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlMAAAGbCAYAAADgEhWsAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nO3deXxV9Z3/8feHhCWyiRJAFgERFQRUiChtFVQcF0DGcSyLtTBajQ9gqr8RlWrLpta2Yhk7RifaKtZdER0GKWoZUHREkoAwslkEFTBCcDouUIUkn98fN02TkOTecO5+X8/H4z6Sc873nvNJTgjvfM+5n2vuLgAAAByZZokuAAAAIJURpgAAAAIgTAEAAARAmAIAAAiAMAUAABBAdqIO3LFjR+/Vq1eiDg8AABCxkpKSfe6eW9+2hIWpXr16qbi4OFGHBwAAiJiZfdzQNi7zAQAABECYAgAACIAwBQAAEABhCgAAIADCFAAAQABhX81nZo9KGi1pr7sPqGe7Sbpf0qWSDkia7O5rgxb25Zdfau/evTp06FDQXSHNNW/eXJ06dVK7du0SXQoAIANF0hphgaQHJP2+ge2XSOpb9ThL0kNVH4/Yl19+qT179qhbt27KyclRKK8Bh3N3/eUvf9Hu3bsliUAFAIi7sJf53P1NSf/byJCxkn7vIaslHW1mxwUpau/everWrZuOOuooghQaZWY66qij1K1bN+3duzfR5QAAMlA07pnqJmlnjeVdVeuO2KFDh5STkxOoKGSWnJwcLgkDABIirjegm9n1ZlZsZsVlZWXhxsapKqQDfl4AAIkSjTC1W1KPGsvdq9Ydxt0fdvc8d8/Lza337W0AAABSSjTC1GJJP7SQsyV94e6lUdgvAABA0gsbpszsGUnvSDrZzHaZ2bVmdoOZ3VA1ZKmk7ZK2SXpE0pSYVZuBJk+erNGjRzfpOSNGjNC0adNiVFHjpk2bphEjRiTk2AAAJELY1gjuPiHMdpc0NWoVpahw9+xMmjRJCxYsaPJ+77//foW+xZFbtGiRmjdv3uRjJcJHH32k3r17q6ioSHl5eYkuBwCAJoukzxQiUFr6tyubS5Ys0XXXXVdrXd1XJx46dCiiwNO+ffsm13LMMcc0+TkAAODI8HYyUdKlS5fqx9FHH11r3TfffKOjjz5azzzzjM4//3zl5OSosLBQn3/+uSZMmKDu3bsrJydHp556qh577LFa+617mW/EiBGaMmWKbr/9dnXs2FGdOnXS9OnTVVlZWWtMzct8vXr10l133aX8/Hy1a9dO3bt317333lvrOB988IGGDx+uVq1a6eSTT9bSpUvVpk2bRmfTKioqNH36dHXo0EEdOnTQTTfdpIqKilpjli1bpnPOOUcdOnTQMccco4suukibN2+u3t67d29J0plnnikzq75EWFRUpL/7u79Tx44d1a5dO33ve9/TO++8E8GZAABkkqmvTFX23GxNfSVxF8kIU3H0k5/8RFOmTNGmTZv093//9/rmm280ePBgLVmyRBs3btSNN96o/Px8LV++vNH9PPXUU8rOztZ///d/64EHHtC//uu/6rnnnmv0OfPnz9fAgQO1du1a3Xbbbbr11lurw0llZaUuv/xyZWdna/Xq1VqwYIHmzJmjb7/9ttF93nfffXrkkUdUWFiod955RxUVFXrqqadqjdm/f79uuukmrVmzRitXrlT79u01ZswYHTx4UJK0Zs0aSaHQVVpaqkWLFkmSvvrqK1199dVatWqV1qxZo9NPP12XXnqpPv/880ZrAgBklsKSQlV4hQpLChNXhLsn5DFkyBBvyKZNmxrc1lRTprhnZYU+xssLL7zgoW9tyI4dO1ySz5s3L+xzx40b59dee2318qRJk3zUqFHVy8OHD/ezzz671nNGjhxZ6znDhw/3qVOnVi/37NnTx48fX+s5J554ot95553u7r5s2TLPysryXbt2VW9/++23XZI/9thjDdZ63HHH+V133VW9XFFR4X379vXhw4c3+Jyvv/7amzVr5qtWrXL3v31vioqKGnyOu3tlZaV36dLFn3jiiQbHRPPnBgCQGqYsmeJZc7J8ypLY/kcvqdgbyDRpPzNVWChVVIQ+JlrdG6wrKip09913a9CgQTr22GPVpk0bLVq0SJ988kmj+xk0aFCt5a5du4Z9K5XGnrNlyxZ17dpV3br9rXH9mWeeqWbNGv7x+OKLL1RaWqphw4ZVr2vWrJnOOqv22zJ++OGHmjhxovr06aN27dqpc+fOqqysDPs17t27V/n5+TrppJPUvn17tW3bVnv37g37PABAZikYVaDymeUqGFWQsBrS/gb0/PxQkMrPT3QlUuvWrWstz5s3T/fdd5/uv/9+DRw4UG3atNHtt98e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lZWl1157Ta+99poGDRqk3/3ud+rbt6/Wr1/f5GM1b9681rKZqbKyMlpfSr3279+viy66SEcddZSeeOIJFRUVadmyZZIU9nLd6NGjVVZWpsLCQr377rtat26dsrOzucwHAEgZaf9qvmRoEDZ48GBt2bJFJ554YoNjzEzDhg3TsGHDNHPmTJ166ql67rnndNppp6lFixaqqKgIXMcpp5yixx9/XH/5y1+qZ6fWrFnT6HP69Omj5s2ba/Xq1TrhhBMkhcLT+++/rz59+kgKzXjt27dPP//5z9W7d29JoRvqa2rRooUk1fo6Pv/8c23ZskUPPvhg9Q3qa9euVXl5eeCvFQCAeEn7malkMHPmTD399NOaOXOm3n//fW3ZskULFy7UrbfeKklavXq17rrrLhUVFemTTz7R4sWLtXPnTvXv319S6FV7H3/8sdauXat9+/bp22+/PaI6Jk6cqKysLF133XXatGmT/vjHP1a/Ms/M6n1OmzZtdO211+q2227T66+/ro0bN+qaa66pFYqOP/54tWzZUg888IC2b9+uV155RT/72c9q7adnz54yM73yyisqKyvT119/rQ4dOqhjx4565JFHtG3bNr3xxhu64YYblJ2d9hkfAJBGCFNxcNFFF+mVV17RihUrNHToUA0dOlS/+MUvdPzxx0uS2rdvr7ffflujR49W3759dfPNN+tnP/uZfvCDH0iSrrjiCl166aW64IILlJubq2eeeeaI6mjbtq3+8z//Uxs3btQZZ5yhW265RbNnz5YktWrVqsHnzZs3T+edd54uv/xynXfeeRowYIDOPffc6u25ubl6/PHH9fLLL6t///6aM2eOfv3rX9faR7du3TRnzhzdcccd6ty5s6ZNm6ZmzZrpueee04YNGzRgwABNnTpVd955p1q2bHlEXx8AIHoyvd1BU1joBvX4y8vL8+Li4nq3bd68Wf369YtzRZnpP/7jP3T55Zdr79696tixY6LLCYSfGwCInuy52arwCmVZlspncvuFmZW4e15925iZyjCPP/64Vq1apY8++khLlizRTTfdpDFjxqR8kAIARFcyvIArVXBzSobZs2ePZs2apdLSUnXp0kWjRo3SL3/5y0SXBQBIMsnwAq5UQZjKMLfeemv1je8AACA4LvMBAAAEQJgCAAAIgDAFAEAGoeVB9BGmAADIIMnwnrXphjAFAEAGoeVB9PFqPgAAMggtD6KPmakUtHDhwlrvpbdgwQK1adMm0D5XrlwpM9O+ffuClgcAQEYhTEXR5MmTZWYyMzVv3lwnnHCCpk+frv3798f0uOPGjdP27dsjHt+rVy/Nmzev1rrvfOc7Ki0t1bHHHhvt8gAASGsRhSkzu9jMtprZNjObUc/2nma23Mw2mNlKM+se/VJTw8iRI1VaWqrt27frrrvu0oMPPqjp06cfNq68vFzRel/EnJwcderUKdA+WrRooS5dutSa8QIAAOGFDVNmliWpQNIlkvpLmmBm/esMmyfp9+4+SNJcSfdEu9BU0bJlS3Xp0kU9evTQxIkTddVVV+nll1/W7NmzNWDAAC1YsEB9+vRRy5YttX//fn3xxRe6/vrr1alTJ7Vt21bDhw9X3TeA/v3vf6+ePXvqqKOO0ujRo7Vnz55a2+u7zLd06VKdddZZysnJ0bHHHqsxY8bom2++0YgRI/Txxx/rlltuqZ5Fk+q/zLdo0SINHDhQLVu2VI8ePXT33XfXCoC9evXSXXfdpfz8fLVr107du3fXvffeW6uOwsJCnXTSSWrVqpU6duyoiy66SOXlvGEmAEQbLQ8SJ5KZqaGStrn7dnc/KOlZSWPrjOkv6b+qPl9Rz/aMlZOTo0OHDkmSduzYoaefflovvPCC1q9fr5YtW2rUqFHavXu3lixZonXr1uncc8/V+eefr9LSUknSu+++q8mTJ+v666/Xe++9pzFjxmjmzJmNHnPZsmW67LLLdOGFF6qkpEQrVqzQ8OHDVVlZqUWLFql79+6aOXOmSktLq49TV0lJia688kr9wz/8g/7nf/5Hv/jFL3TPPffogQceqDVu/vz5GjhwoNauXavbbrtNt956q9555x1JUnFxsaZOnapZs2Zp69atWr58uS6++OKg31IAQD1oeZBA7t7oQ9I/SvptjeWrJT1QZ8zTkm6s+vwfJLmkY+vZ1/WSiiUVH3/88d6QTZs2NbityaZMcc/KCn2MsUmTJvmoUaOql999910/9thj/fvf/77PmjXLs7Oz/bPPPqvevnz5cm/durUfOHCg1n5OO+00/+Uvf+nu7hMmTPCRI0fW2n7ttdd66NSFPPbYY966devq5e985zs+bty4Buvs2bOn33vvvbXWrVixwiV5WVmZu7tPnDjRzzvvvFpjZs2a5d26dau1n/Hjx9cac+KJJ/qdd97p7u4vvviit2vXzr/88ssGa4mmqP7cAECKmbJkimfNyfIpS2L//10mklTsDWSlaN2APl3ScDNbJ2m4pN2SKuoJbg+7e5675+Xm5kbp0GEUFkoVFaGPcbBs2TK1adNGrVq10rBhw3Tuuefq3/7t3yRJ3bt3V+fOnavHlpSU6MCBA8rNzVWbNm2qH++//74+/PBDSdLmzZs1bNiwWseou1zXunXrdMEFFwT6OjZv3qzvfve7tdZ973vf0+7du/Xll19Wrxs0aFCtMV27dtXevXslSRdeeKF69uyp3r1766qrrtLjjz+ur776KlBdAID6FYwqUPnMctoeJEAkfaZ2S+pRY7l71bpq7v6pQjNSMrM2kq5w9/+LVpGB5OeHglR+fJqTnXvuuXr44YfVvHlzde3aVc2bN6/e1rp161pjKysr1blzZ61ateqw/bRr1y7mtR6pmjep1/z6/rqtsrJSktS2bVutXbtWb775pl5//XXdc889uv3221VUVKSuXbvGtWYAAGIlkpmpIkl9zay3mbWQNF7S4poDzKyjmf11Xz+R9Gh0ywygoEAqLw99jIOjjjpKJ554onr27HlY0Khr8ODB2rNnj5o1a6YTTzyx1uOvr87r16+fVq9eXet5dZfrOuOMM7R8+fIGt7do0UIVFYdNHNbSr18/vf3227XWvfXWW+revbvatm3b6HNrys7O1vnnn6977rlHGzZs0P79+7VkyZKInw8AQLILG6bcvVzSNEmvStos6Xl332hmc83ssqphIyRtNbMPJHWWdHeM6k0rI0eO1He/+12NHTtWf/jDH7Rjxw698847mjVrVvVs1Y9//GP98Y9/1D333KM//elPeuSRR/TSSy81ut877rhDL7zwgn76059q06ZN2rhxo+bPn68DBw5ICr0Kb9WqVdq9e3eDTTpvvvlmvfHGG5o9e7Y++OADPfXUU7rvvvt06623Rvz1LVmyRPfff7/WrVunjz/+WE8//bS++uor9evXL+J9AACQ7CK6Z8rdl7r7Se7ex93vrlo3090XV32+0N37Vo35kbt/G8ui04WZaenSpTr//PN13XXX6eSTT9b3v/99bd26tfoy2Nlnn63f/e53euihhzRo0CAtWrRIs2fPbnS/l156qV566SX94Q9/0BlnnKHhw4drxYoVatYsdLrnzp2rnTt3qk+fPmro3rXB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\n",
            "text/plain": [
              "<Figure size 720x504 with 1 Axes>"
            ]
          },
          "metadata": {
            "needs_background": "light"
          }
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "plot_predictions(predictions=y_preds_new);"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 428
        },
        "id": "9y-u_rVC16XJ",
        "outputId": "354933d7-7c14-46c5-9a71-eee4cb7200c2"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/png": "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u3dvoqcCAEiQVKs8iBRhKkp27dpV+pgxY8Zx65588smo7zM7O1sNGzaM+usCAFAjEd6ll2qVB5EiTEVJmzZtSh/Nmzc/bt0777yjPn36qEGDBurcubPuueceHTlypPT5c+bMUc+ePZWVlaWTTjpJ/fr10+7duzVz5kzdf//9Wrt2belZrpkzZ0o6/m0+M9Mzzzyja665Ro0aNdKpp56qF198sdw8ly9frt69e6tBgwY699xzNX/+fJmZlixZUuXPdvDgQY0aNUqNGzdW69at9Ytf/OK4MS+++KLOO+88NWnSRK1atdI111yjnTt3SpK2bt2q73znO5JCAbDsmboFCxbo4osvVosWLXTSSSfp8ssv1/r162v85w8ASKAI79JLtcqDSBGm4mDhwoW6/vrrNX78eK1du1bPPvusZs+erbvvvluS9Pnnn2v48OEaOXKk1q9fr3feeUc33HCDJGnYsGG6/fbbdcYZZ5Se5Ro2bFiV+5o6daqGDh2q1atXa9iwYbrxxhv16aefSpL279+vwYMHq1u3blqxYoUeffRR3XHHHWHnP2HCBP3lL3/R66+/rkWLFmnlypV65513yo05cuSI7r//fq1evVrz5s3T3r17NWLECElS+/bt9frrr0uS1q5dW+5M3YEDB3Tbbbfpgw8+0JIlS9SsWTMNGTKkXNAEACS5WnqXXsTcPSGPPn36eFXWrVtX5baaGjtvrGfcn+Fj542N2muGM2vWLA/90YZcfPHFPnXq1HJj3njjDW/UqJEXFxf7ihUrXJJv3bq10tebPHmyn3XWWcet79ixoz/22GOly5J84sSJpctHjx71rKwsf+GFF9zd/be//a23aNHCDx48WDrmpZdeckm+ePHiSve9b98+r1evnr/44ovl1jVr1sxHjhxZ5Z/B+vXrXZJv377d3d0XL17skrywsLDK57i779+/3+vUqePvvvtuteMqE83fGwAAypJU4FVkmlp/ZioZLnZbsWKFHnroITVu3Lj0cd111+nAgQP6/PPPdc4552jAgAHq0aOHrr76aj399NMqLCw8oX317Nmz9PvMzExlZ2drz549kqQNGzaoR48eysrKKh1zwQUXVPt6H3/8sY4cOaK+ffuWrmvcuLHOPvvscuM+/PBDDR06VB07dlSTJk2Um5srSaVnxap7/euuu05dunRR06ZN1bp1axUXF4d9HgAAyaLWh6lkuNituLhYkydP1qpVq0ofH330kf7xj38oOztbGRkZ+vOf/6w///nP6tmzp37/+9+ra9euWr16dY33Vbdu3XLLZqbi4uJo/SiVOnDggC6//HI1bNhQL7zwgvLz87VgwQJJCvt23eDBg1VYWKjp06dr+fLlWrlypTIzM3mbDwBSSG2tPIhUrQ9TyXCxW+/evbVhwwaddtppxz0yM0O9qWamvn37avLkycrPz1fbtm316quvSpLq1aunY8eOBZ5Ht27dtGbNGn399del6z744INqn9OlSxfVrVtXy5YtK1134MABrVmzpnR5w4YN2rt3r37xi1/okksuUbdu3UrPhn2jXr16klTu5/jiiy+0YcMG3X333RowYIC6d++uffv2qaioKNDPCQCIr2R4FyiRan2YSgaTJk3Syy+/rEmTJmnNmjXasGGDZs+erTvvvFOStGzZMj344IPKz8/Xp59+qrlz52r79u0688wzJYXu2tu2bZs+/PBD7d27V4cPHz6heVx33XXKyMjQzTffrHXr1umvf/1r6Z15Zlbpcxo3bqybbrpJd911l/7yl79o7dq1uvHGG8uFog4dOqh+/fp66qmntGXLFr311lu67777yr1Ox44dZWZ66623VFhYqP3796tFixZq2bKlZsyYoc2bN+vtt9/WLbfcUhowAQAJFGHdgZQc7wIlVFUXU8X6Ea8L0BOh4gXo7u4LFy70iy66yLOysrxJkybep08f//Wvf+3uoZ934MCB3qpVK69Xr5536dLFH3nkkdLnHjp0yK+++mpv3ry5S/LnnnvO3Su/AH3WrFnl9ltxzNKlS71Xr15er14979Wrl8+ePdsl+bJly6r8efbv3+833HCDN2rUyLOzs33q1Kk+aNCgchegv/LKK37qqad6/fr1/bzzzvMFCxYcd2H71KlTvU2bNm5mpc9dtGiRn3XWWV6/fn0/66yzfMGCBd6oUaPSn7EmUv33BgCSSkaGuxT6imovQLfQ9vjLzc31goKCSretX79e3bt3j/OM0tN///d/6wc/+IH27Nmjli1bJno6gfB7AwBRNG5cqDdqzJha+Xl6NWVmK9w9t7JtvJ+SZp5//nmdeuqpat++vdasWaPbbrtNQ4YMSfkgBQCIslr6ocSxQJhKM7t379bkyZO1a9cutWnTRoMGDdIjjzyS6GkBAJCyuAA9zdx5553aunWrDh8+rG3btuk3v/mNmjRpkuhpAQCSTLrXHdQEYQoAABwn3esOaoIwBQBAOomw8iDt6w5qgLv5UGvwewMAEcjMlI4dC30wMSXJEavubj7OTAEAkE7GjAkFqTGccYoW7uYDACCdUHkQdZyZAgAACIAwlYJmz55d7rP0Zs6cqcaNGwd6zSVLlsjMtHfv3qDTAwAkMSoPoo8wFUWjRo2SmcnMVLduXZ166qmaMGGCDhw4ENP9Dhs2TFu2bIl4fKdOnfT444+XW/etb31Lu3bt0sknnxzt6QEAkgiVB9FHmIqyAQMGaNeuXdqyZYsefPBB/eY3v9GECROOG1dUVKRo3UmZlZWlVq1aBXqNevXqqU2bNuXOeAEAUgiVBwlDmIqy+vXrq02bNmrfvr2uu+46XX/99XrzzTc1ZcoU9ejRQzNnzlSXLl1Uv359HThwQF9++aVGjx6tVq1aqUmTJurXr58qVkb84Q9/UMeOHdWwYUMNHjxYu3fvLre9srf55s+frwsuuEBZWVk6+eSTNWTIEB06dEj9+/fXtm3bdMcdd5SeRZMqf5tvzpw5Ovvss1W/fn21b99eDz30ULkA2KlTJz344IMaM2aMmjZtqpycHD322GPl5jF9+nSdfvrpatCggVq2bKnLL79cRdyKCwDRN316qPJgevVnnPIG5aloUpHyBnERerQQpmIsKytLR48elSR98sknevnllzVr1iytXr1a9evX16BBg7Rz507NmzdPK1eu1CWXXKJLL71Uu3btkiQtX75co0aN0ujRo7Vq1SoNGTJEkyZNqnafCxYs0FVXXaXLLrtMK1as0OLFi9WvXz8VFxdrzpw5ysnJ0aRJk7Rr167S/VS0YsUKXXPNNfrhD3+ov//97/rlL3+phx9+WE899VS5cdOmTdPZZ5+tDz/8UHfddZfuvPNOLV26VJJUUFCgcePGafLkydq4caMWLVqkgQMHBv0jBQBUhsqDxHH3hDz69OnjVVm3bl2V22ps7Fj3jIzQ1xgbOXKkDxo0qHR5+fLlfvLJJ/u1117rkydP9szMTP/8889Lty9atMgbNWrkBw8eLPc655xzjj/yyCPu7j5ixAgfMGBAue033XSThw5dyHPPPeeNGjUqXf7Wt77lw4YNq3KeHTt29Mcee6zcusWLF7skLywsdHf36667zr/zne+UGzN58mRv165dudcZPnx4uTGnnXaaP/DAA+7u/vrrr3vTpk39q6++qnIu0RTV3xsAAMqQVOBVZJqIzkyZ2UAz22hmm81sYiXbO5rZIjP7yMyWmFlOlDPfiYvwtGe0LFiwQI0bN1aDBg3Ut29fXXLJJfr1r38tScrJyVHr1q1Lx65YsUIHDx5Udna2GjduXPpYs2aNPv74Y0mhVu++ffuW20fF5YpWrlyp7373u4F+jvXr1+vb3/52uXUXXXSRdu7cqa+++qp0Xc+ePcuNadu2rfbs2SNJuuyyy9SxY0d17txZ119/vZ5//nnt27cv0LwAAEg2YcOUmWVIypN0haQzJY0wszMrDHtc0h/cvaekqZIejvZET1icT3tecsklWrVqlTZu3KhDhw5pzpw5pReHN2rUqNzY4uJitW7dWqtWrSr32LBhgx544IG4zPdElL1IvW7dusdtKy4uliQ1adJEH374oV577TV16NBBDz/8sLp166bPPvssrvMFgHRA5UHiRHJm6nxJm919i7sfkfSKpKEVxpwp6X9Kvl9cyfbEycsLffZQnNpeGzZsqNNOO00dO3Y8LmhU1Lt3b+3evVt16tTRaaedVu7xTQDr3r27li1bVu55FZcrOvfcc7Vo0aIqt9erV0/Hjh2r9jW6d++u999/v9y69957Tzk5OWrSpEm1zy0rMzNTl156qR5++GF99NFHOnDggObNmxfx8wEAkaHyIHEiCVPtJG0vs7yjZF1ZqyX9sOT7H0hqYmbHFRaZ2WgzKzCzgsLCwhOZb60yYMAAffvb39bQoUP1pz/9SZ988omWLl2qyZMn691335Uk/fSnP9Vf//pXPfzww/rHP/6hGTNm6I033qj2de+55x7NmjVL9957r9atW6e1a9dq2rRpOnjwoKTQXXjvvvuudu7cWWVJ5+233663335bU6ZM0aZNm/TSSy/piSee0J133hnxzzdv3jw9+eSTWrlypbZt26aXX35Z+/bt48OIASBSEdYdSFQeJFK07uabIKmfma2U1E/STknHnfpw92fcPdfdc7Ozs6O069RlZpo/f74uvfRS3XzzzTrjjDN07bXXauPGjWrbtq0k6cILL9Tvf/97Pf300+rZs6fmzJmjKVOmVPu6V155pd544w396U9/0rnnnqt+/fpp8eLFqlMndLinTp2q7du3q0uXLqrqOPTu3VuzZs3S66+/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\n",
            "text/plain": [
              "<Figure size 720x504 with 1 Axes>"
            ]
          },
          "metadata": {
            "needs_background": "light"
          }
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Saving a model in PyTorch\n",
        "\n",
        "There are three main methods you should about for saving and loading models in PyTorch.\n",
        "\n",
        "1. `torch.save()` - allows you save a PyTorch object in Python's pickle format \n",
        "2. `torch.load()` - allows you load a saved PyTorch object\n",
        "3. `torch.nn.Module.load_state_dict()` - this allows to load a model's saved state dictionary \n",
        "\n",
        "PyTorch save & load code tutorial + extra-curriculum - https://pytorch.org/tutorials/beginner/saving_loading_models.html#saving-loading-model-for-inference"
      ],
      "metadata": {
        "id": "GXusQ2JP1_S1"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Saving our PyTorch model\n",
        "from pathlib import Path\n",
        "\n",
        "# 1. Create models directory \n",
        "MODEL_PATH = Path(\"models\")\n",
        "MODEL_PATH.mkdir(parents=True, exist_ok=True)\n",
        "\n",
        "# 2. Create model save path\n",
        "MODEL_NAME = \"01_pytorch_workflow_model_0.pth\"\n",
        "MODEL_SAVE_PATH = MODEL_PATH / MODEL_NAME\n",
        "\n",
        "# 3. Save the model state dict\n",
        "print(f\"Saving model to: {MODEL_SAVE_PATH}\")\n",
        "torch.save(obj=model_0.state_dict(),\n",
        "           f=MODEL_SAVE_PATH)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "1a0iaBiX5JAG",
        "outputId": "dbe254f6-1696-4fa8-a3d3-619aebf930aa"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Saving model to: models/01_pytorch_workflow_model_0.pth\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "!ls -l models"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "Os6BGzXT54Xq",
        "outputId": "2529729e-a3c5-486a-e913-b3031a5fe9ab"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "total 4\n",
            "-rw-r--r-- 1 root root 1063 Mar  8 03:36 01_pytorch_workflow_model_0.pth\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Loading a PyTorch model\n",
        "\n",
        "Since we saved our model's `state_dict()` rather the entire model, we'll create a new instance of our model class and load the saved `state_dict()` into that. "
      ],
      "metadata": {
        "id": "uib6vQMB7Ds1"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "model_0.state_dict()"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "9U-uXVaC85PP",
        "outputId": "a2c8ea18-fbb5-49a7-d985-eab4a76b2875"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "OrderedDict([('weights', tensor([0.6990])), ('bias', tensor([0.3093]))])"
            ]
          },
          "metadata": {},
          "execution_count": 28
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# To load in a saved state_dict we have to instantiate a new instance of our model class\n",
        "loaded_model_0 = LinearRegressionModel()\n",
        "\n",
        "# Load the saved state_dict of model_0 (this will update the new instance with updated parameters)\n",
        "loaded_model_0.load_state_dict(torch.load(f=MODEL_SAVE_PATH))"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "lTghUxOH89lz",
        "outputId": "354c3ac5-96d5-499b-d734-d3e56b8ba3e7"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<All keys matched successfully>"
            ]
          },
          "metadata": {},
          "execution_count": 29
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "loaded_model_0.state_dict()"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "_44x4te89OjW",
        "outputId": "a8537d36-f89a-4409-90b3-3563f836231e"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "OrderedDict([('weights', tensor([0.6990])), ('bias', tensor([0.3093]))])"
            ]
          },
          "metadata": {},
          "execution_count": 30
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Make some predictions with our loaded model\n",
        "loaded_model_0.eval()\n",
        "with torch.inference_mode():\n",
        "  loaded_model_preds = loaded_model_0(X_test)\n",
        "\n",
        "loaded_model_preds"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "BxPDLIU09Q0i",
        "outputId": "887f395e-1719-4f68-f3d9-23e17088eb14"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "tensor([[0.8685],\n",
              "        [0.8825],\n",
              "        [0.8965],\n",
              "        [0.9105],\n",
              "        [0.9245],\n",
              "        [0.9384],\n",
              "        [0.9524],\n",
              "        [0.9664],\n",
              "        [0.9804],\n",
              "        [0.9944]])"
            ]
          },
          "metadata": {},
          "execution_count": 31
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Make some models preds\n",
        "model_0.eval()\n",
        "with torch.inference_mode():\n",
        "  y_preds = model_0(X_test)\n",
        "\n",
        "y_preds"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "3BP9cufq99K-",
        "outputId": "191daad3-6df1-4018-95c9-de755eb4f381"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "tensor([[0.8685],\n",
              "        [0.8825],\n",
              "        [0.8965],\n",
              "        [0.9105],\n",
              "        [0.9245],\n",
              "        [0.9384],\n",
              "        [0.9524],\n",
              "        [0.9664],\n",
              "        [0.9804],\n",
              "        [0.9944]])"
            ]
          },
          "metadata": {},
          "execution_count": 32
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Compare loaded model preds with original model preds\n",
        "y_preds == loaded_model_preds"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "sSbACbvI94XX",
        "outputId": "853b8d5f-7830-41c2-ffd2-93a7fc0b2270"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "tensor([[True],\n",
              "        [True],\n",
              "        [True],\n",
              "        [True],\n",
              "        [True],\n",
              "        [True],\n",
              "        [True],\n",
              "        [True],\n",
              "        [True],\n",
              "        [True]])"
            ]
          },
          "metadata": {},
          "execution_count": 33
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## 6. Putting it all together\n",
        "\n",
        "Let's go back through the steps above and see it all in one place."
      ],
      "metadata": {
        "id": "yQHmmLBo9_ji"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Import PyTorch and matplotlib\n",
        "import torch\n",
        "from torch import nn\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "# Check PyTorch version\n",
        "torch.__version__"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 35
        },
        "id": "TY16oebx_4yK",
        "outputId": "d0d09199-ab74-45b0-fa8f-ee86ecaa5f3b"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            },
            "text/plain": [
              "'1.10.0+cu111'"
            ]
          },
          "metadata": {},
          "execution_count": 34
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "Create device-agnostic code.\n",
        "\n",
        "This means if we've got access to a GPU, our code will use it (for potentially faster computing).\n",
        "\n",
        "If no GPU is available, the code will default to using CPU."
      ],
      "metadata": {
        "id": "l91cJBlNAZ7m"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Setup device agnostic code\n",
        "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
        "print(f\"Using device: {device}\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "0hRCrpBhAj9G",
        "outputId": "d1f41115-e110-49ba-822b-748f48d86bd3"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Using device: cuda\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 6.1 Data "
      ],
      "metadata": {
        "id": "7lMKxKvN_1yP"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Create some data using the linear regression formula of y = weight * X + bias\n",
        "weight = 0.7\n",
        "bias = 0.3\n",
        "\n",
        "# Create range values\n",
        "start = 0\n",
        "end = 1\n",
        "step = 0.02\n",
        "\n",
        "# Create X and y (features and labels)\n",
        "X = torch.arange(start, end, step).unsqueeze(dim=1) # without unsqueeze, errors will pop up\n",
        "y = weight * X + bias\n",
        "X[:10], y[:10]"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "StBcwzuA_4GP",
        "outputId": "620dca7c-3409-47f9-9a50-f051505125d3"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "(tensor([[0.0000],\n",
              "         [0.0200],\n",
              "         [0.0400],\n",
              "         [0.0600],\n",
              "         [0.0800],\n",
              "         [0.1000],\n",
              "         [0.1200],\n",
              "         [0.1400],\n",
              "         [0.1600],\n",
              "         [0.1800]]), tensor([[0.3000],\n",
              "         [0.3140],\n",
              "         [0.3280],\n",
              "         [0.3420],\n",
              "         [0.3560],\n",
              "         [0.3700],\n",
              "         [0.3840],\n",
              "         [0.3980],\n",
              "         [0.4120],\n",
              "         [0.4260]]))"
            ]
          },
          "metadata": {},
          "execution_count": 36
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Split data\n",
        "train_split = int(0.8 * len(X))\n",
        "X_train, y_train = X[:train_split], y[:train_split]\n",
        "X_test, y_test = X[train_split:], y[train_split:]\n",
        "len(X_train), len(y_train), len(X_test), len(y_test)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "mGpxaIDsCDBN",
        "outputId": "8ff5f597-3f3f-4324-c015-8b6b641163de"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "(40, 40, 10, 10)"
            ]
          },
          "metadata": {},
          "execution_count": 37
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "def plot_predictions(train_data=X_train,\n",
        "                     train_labels=y_train,\n",
        "                     test_data=X_test,\n",
        "                     test_labels=y_test,\n",
        "                     predictions=None):\n",
        "  \"\"\"\n",
        "  Plots training data, test data and compares predictions.\n",
        "  \"\"\"\n",
        "  plt.figure(figsize=(10, 7))\n",
        "\n",
        "  # Plot training data in blue\n",
        "  plt.scatter(train_data, train_labels, c=\"b\", s=4, label=\"Training data\")\n",
        "\n",
        "  # Plot test data in green\n",
        "  plt.scatter(test_data, test_labels, c=\"g\", s=4, label=\"Testing data\")\n",
        "\n",
        "  # Are there predictions?\n",
        "  if predictions is not None:\n",
        "    # Plot the predictions if they exist\n",
        "    plt.scatter(test_data, predictions, c=\"r\", s=4, label=\"Predictions\")\n",
        "  \n",
        "  # Show the legend\n",
        "  plt.legend(prop={\"size\": 14});"
      ],
      "metadata": {
        "id": "T4tidX_5CnVx"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "# Plot the data\n",
        "# Note: if you don't have the plot_predictions() function loaded, this will error\n",
        "plot_predictions(X_train, y_train, X_test, y_test)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 428
        },
        "id": "2go898QeCXJY",
        "outputId": "ca6406c0-6604-4ff3-8dd4-25aae5a44333"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlMAAAGbCAYAAADgEhWsAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nO3deXRVhd3u8eeXhCEyxNgE1IBAEQdEVIgo69aCQ+sASr3evgKtQrUaF+R95a1jtUVB7W0Va/Ua22BrsWoVpdhS4IrWQh0qkoCFawBtRCpgSgJtUbQakvzuHydNk5jknLDPfL6ftbKSPZyzf2QzPOyzzxNzdwEAAODgZCV6AAAAgFRGmAIAAAiAMAUAABAAYQoAACAAwhQAAEAAOYk6cEFBgQ8dOjRRhwcAAIjY+vXr97h7YUfbEhamhg4dqsrKykQdHgAAIGJm9pfOtvEyHwAAQACEKQAAgAAIUwAAAAEQpgAAAAIgTAEAAAQQ9t18ZvaIpMmSat19VAfbTdL9ki6Q9LGkme6+IehgH3zwgWpra3XgwIGgT4U016NHDw0YMED9+/dP9CgAgAwUSTXCIkkPSvpFJ9vPlzSi+eM0ST9u/nzQPvjgA+3evVtFRUXKzc1VKK8Bn+Xu+uc//6ldu3ZJEoEKABB3YV/mc/eXJP2ti12mSPqFh6yVdKiZHRFkqNraWhUVFemQQw4hSKFLZqZDDjlERUVFqq2tTfQ4AIAMFI17pook7Wi1vLN53UE7cOCAcnNzAw2FzJKbm8tLwgCAhIjrDehmdrWZVZpZZV1dXbh94zQV0gG/XwAAiRKNMLVL0uBWy4Oa132Guy9092J3Ly4s7PDH2wAAAKSUaISpZZIut5DTJe1z95ooPC8AAEDSCxumzOxJSa9JOtbMdprZlWZ2jZld07zLSknbJFVLeljSrJhNm4FmzpypyZMnd+sxEydOVGlpaYwm6lppaakmTpyYkGMDAJAIYasR3H1amO0uaXbUJkpR4e7ZmTFjhhYtWtTt573//vsV+hZHbunSperRo0e3j5UI27dv17Bhw1RRUaHi4uJEjwMAQLdF0jOFCNTU/PuVzeXLl+uqq65qs679uxMPHDgQUeDJy8vr9iyHHXZYtx8DAAAODj9OJkoOP/zwlo9DDz20zbpPPvlEhx56qJ588kmdddZZys3NVXl5ufbu3atp06Zp0KBBys3N1QknnKCf//znbZ63/ct8EydO1KxZs3TLLbeooKBAAwYM0PXXX6+mpqY2+7R+mW/o0KG68847VVJSov79+2vQoEG655572hzn7bff1oQJE9S7d28de+yxWrlypfr27dvl1bTGxkZdf/31ys/PV35+vubMmaPGxsY2+zz33HM644wzlJ+fr8MOO0znnnuutmzZ0rJ92LBhkqRTTz1VZtbyEmFFRYW+/OUvq6CgQP3799cXvvAFvfbaaxGcCQBAJpm9YrZy5udo9orEvUhGmIqjb3/725o1a5Y2b96sr3zlK/rkk080ZswYLV++XFVVVbr22mtVUlKiF198scvneeKJJ5STk6M//vGPevDBB/WjH/1Iixcv7vIx9913n0488URt2LBBN910k2688caWcNLU1KSLL75YOTk5Wrt2rRYtWqR58+bp008/7fI57733Xj388MMqLy/Xa6+9psbGRj3xxBNt9vnoo480Z84crVu3TmvWrFFeXp4uvPBC1dfXS5LWrVsnKRS6ampqtHTpUknShx9+qMsuu0wvv/yy1q1bp5NPPlkXXHCB9u7d2+VMAIDMUr6+XI3eqPL15Ykbwt0T8jF27FjvzObNmzvd1l2zZrlnZ4c+x8szzzzjoW9tyLvvvuuSfMGCBWEfe+mll/qVV17ZsjxjxgyfNGlSy/KECRP89NNPb/OYc845p81jJkyY4LNnz25ZHjJkiE+dOrXNY44++mi/44473N39ueee8+zsbN+5c2fL9ldffdUl+c9//vNOZz3iiCP8zjvvbFlubGz0ESNG+IQJEzp9zP79+z0rK8tffvlld//396aioqLTx7i7NzU1+eGHH+6PPfZYp/tE8/cNACA1zFo+y7PnZfus5bH9h15SpXeSadL+ylR5udTYGPqcaO1vsG5sbNRdd92l0aNH63Of+5z69u2rpUuX6r333uvyeUaPHt1m+cgjjwz7o1S6eszWrVt15JFHqqjo38X1p556qrKyOv/tsW/fPtXU1Gj8+PEt67KysnTaaW1/LOM777yj6dOna/jw4erfv78GDhyopqamsL/G2tpalZSU6JhjjlFeXp769eun2trasI8DAGSWskllapjboLJJZQmbIe1vQC8pCQWpkpJETyL16dOnzfKCBQt077336v7779eJJ56ovn376pZbbgkbjNrfuG5mbe6ZitZjomHy5MkaNGiQysvLVVRUpJycHI0cObLlZb7OzJgxQ7t379Z9992noUOHqlevXjr77LPDPg4AgHhL+zBVVhb6SEavvPKKLrzwQl122WWSQi+5vv322y03sMfLcccdp/fff1/vv/++jjzySElSZWVll2ErLy9PRxxxhNauXauzzjpLUmj+devW6YgjQj/neu/evdq6daseeughnXnmmZKkDRs2qKGhoeV5evbsKUmfuXH9lVde0QMPPKBJkyZJknbv3t3m3ZEAACSLtH+ZL5kdc8wxevHFF/XKK69o69atKi0t1bvvvhv3Ob70pS/p2GOP1YwZM7Rx40atXbtW3/rWt5STk9Nlf9a1116ru+++W0uWLNFbb72lOXPmtAk8+fn5Kigo0MMPP6zq6mr94Q9/0DXXXKOcnH9n+AEDBig3N1erVq3S7t27tW/fPkmh783jjz+uzZs3q6KiQlOnTm0JXgAAJBPCVAJ95zvf0bhx43T++efri1/8ovr06aOvfe1rcZ8jKytLzz77rD799FONGzdOM2bM0K233iozU+/evTt93HXXXadvfOMb+uY3v6nTTjtNTU1NbebPysrS4sWLtWnTJo0aNUqzZ8/WHXfcoV69erXsk5OTowceeEA//elPdeSRR2rKlCmSpEceeUT79+/X2LFjNXXqVF1xxRUaOnRozL4HAIDkkQx1B91h3s127WgpLi72ysrKDrdt2bJFxx9/fJwnQmsbN27UySefrMrKSo0dOzbR40SE3zcAkB5y5ueo0RuVbdlqmNsQ/gFxYGbr3b3DH9XBlSlIkp599lk9//zzevfdd7V69WrNnDlTJ510ksaMGZPo0QAAGaZkbImyLVslY5Pg3WMRSPsb0BGZDz/8UDfddJN27Nih/Px8TZw4Uffdd1/YnzkIAEC0lU0qS2jVQXcRpiBJuvzyy3X55ZcnegwAAFIOL/MBAAAEQJgCAAAIgDAFAADiItUqDyJFmAIAAHFRvr5cjd6o8vVJ8ANzo4gwBQAA4iLVKg8ixbv5AABAXKRa5UGkuDKVwoYOHaoFCxYk5NiTJ0/WzJkzE3JsAACSCWEqSsysy48gweP222/XqFGjPrO+oqJCs2bNCjB1/KxZs0Zmpj179iR6FAAAooqX+aKkpqam5evly5frqquuarMuNzc36scsLCyM+nMCAIDu4cpUlBx++OEtH4ceeuhn1r300ksaO3asevfurWHDhunWW29VfX19y+OXLl2q0aNHKzc3V4cddpgmTJig3bt3a9GiRZo3b56qqqparnItWrRI0mdf5jMzLVy4UF/96lfVp08fff7zn9fjjz/eZs7XX39dY8aMUe/evXXKKado5cqVMjOtWbOm01/bxx9/rJkzZ6pv374aOHCgvve9731mn8cff1ynnnqq+vXrpwEDBuirX/2qdu3aJUnavn27zjzzTEmhANj6St1zzz2nM844Q/n5+TrssMN07rnnasuWLd3+/gMAEiddKw8iRZiKg1WrVulrX/uaSktLVVVVpUceeURLlizRLbfcIkn661//qqlTp2rGjBnasmWLXnrpJV122WWSpEsvvVTXXXedjj32WNXU1KimpkaXXnppp8eaP3++pkyZoo0bN+rSSy/VFVdcoffee0+StH//fk2ePFnHHXec1q9fr7vvvls33HBD2Pmvv/56vfDCC/rVr36lF198UW+88YZeeumlNvvU19dr3rx52rhxo5YvX649e/Zo2rRpkqTBgwfrV7/6lSSpqqpKNTU1uv/++yVJH330kebMmaN169ZpzZo1ysvL04UXXtgmaAIAklu6Vh5EzN0T8jF27FjvzObNmzvd1l2zls/y7HnZPmv5rKg9ZzjPPPOMh761IWeccYbPnz+/zT7PPvus9+nTx5uamnz9+vUuybdv397h8912221+wgknfGb9kCFD/J577mlZluQ333xzy/KBAwc8NzfXH3vsMXd3/8lPfuL5+fn+8ccft+zzxBNPuCRfvXp1h8f+8MMPvWfPnv7444+3WZeXl+czZszo9HuwZcsWl+Q7duxwd/fVq1e7JK+rq+v0Me7u+/fv96ysLH/55Ze73K8j0fx9AwCIXCL+rY03SZXeSaZJ+ytTyZCW169fr7vuukt9+/Zt+Zg+fbo++ugj/fWvf9VJJ52kc845R6NGjdIll1yiH//4x6qrqzuoY40ePbrl65ycHBUWFqq2tlaStHXrVo0aNarN/VunnXZal8/3zjvvqL6+XuPHj29Z17dvX5144olt9tuwYYOmTJmiIUOGqF+/fiouLpaklqtiXT3/9OnTNXz4cPXv318DBw5UU1NT2McBAJJH2aQyNcxtSMvag0ikfZhKhoKwpqYm3XbbbfrTn/7U8rFp0yb9+c9/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\n",
            "text/plain": [
              "<Figure size 720x504 with 1 Axes>"
            ]
          },
          "metadata": {
            "needs_background": "light"
          }
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 6.2 Building a PyTorch Linear model"
      ],
      "metadata": {
        "id": "X2jU9bN1Cgt0"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Create a linear model by subclassing nn.Module\n",
        "class LinearRegressionModelV2(nn.Module):\n",
        "  def __init__(self):\n",
        "    super().__init__()\n",
        "    # Use nn.Linear() for creating the model parameters / also called: linear transform, probing layer, fully connected layer, dense layer\n",
        "    self.linear_layer = nn.Linear(in_features=1,\n",
        "                                  out_features=1)\n",
        "    \n",
        "  def forward(self, x: torch.Tensor) -> torch.Tensor:\n",
        "    return self.linear_layer(x)\n",
        "\n",
        "# Set the manual seed\n",
        "torch.manual_seed(42)\n",
        "model_1 = LinearRegressionModelV2()\n",
        "model_1, model_1.state_dict()"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "wx8c6kF5Cyp_",
        "outputId": "eeb9bcc0-a227-4cca-cc4c-96021e6c0d23"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "(LinearRegressionModelV2(\n",
              "   (linear_layer): Linear(in_features=1, out_features=1, bias=True)\n",
              " ),\n",
              " OrderedDict([('linear_layer.weight', tensor([[0.7645]])),\n",
              "              ('linear_layer.bias', tensor([0.8300]))]))"
            ]
          },
          "metadata": {},
          "execution_count": 40
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "model_1.state_dict()"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "v9DmOPoTFIC4",
        "outputId": "4f37c379-8cb8-4080-9615-f2d936abd829"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "OrderedDict([('linear_layer.weight', tensor([[0.7645]])),\n",
              "             ('linear_layer.bias', tensor([0.8300]))])"
            ]
          },
          "metadata": {},
          "execution_count": 41
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "X_train[:5], y_train[:5]"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "iq9DxUEyEEvX",
        "outputId": "66921f3d-c706-40dc-99ed-6b35f7ec92dc"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "(tensor([[0.0000],\n",
              "         [0.0200],\n",
              "         [0.0400],\n",
              "         [0.0600],\n",
              "         [0.0800]]), tensor([[0.3000],\n",
              "         [0.3140],\n",
              "         [0.3280],\n",
              "         [0.3420],\n",
              "         [0.3560]]))"
            ]
          },
          "metadata": {},
          "execution_count": 42
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Check the model current device\n",
        "next(model_1.parameters()).device"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "x3gCbh6Am8go",
        "outputId": "f2121256-84b6-4908-d3bd-084ac636a156"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "device(type='cpu')"
            ]
          },
          "metadata": {},
          "execution_count": 45
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Set the model to use the target device\n",
        "model_1.to(device)\n",
        "next(model_1.parameters()).device"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "iMNMpAavEFnC",
        "outputId": "d3aa990d-455f-4808-fc01-b229be4eb853"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "device(type='cuda', index=0)"
            ]
          },
          "metadata": {},
          "execution_count": 46
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "model_1.state_dict() "
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "9h7xgGbCnWI1",
        "outputId": "74a3d084-6c7b-443b-e217-401985507575"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "OrderedDict([('linear_layer.weight', tensor([[0.7645]], device='cuda:0')),\n",
              "             ('linear_layer.bias', tensor([0.8300], device='cuda:0'))])"
            ]
          },
          "metadata": {},
          "execution_count": 47
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 6.3 Training\n",
        "\n",
        "For training we need:\n",
        "* Loss function\n",
        "* Optimizer\n",
        "* Training loop\n",
        "* Testing loop"
      ],
      "metadata": {
        "id": "iEfyHhtrm45n"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Setup loss function\n",
        "loss_fn = nn.L1Loss() # same as MAE\n",
        "\n",
        "# Setup our optimizer\n",
        "optimizer = torch.optim.SGD(params=model_1.parameters(), \n",
        "                            lr=0.01)"
      ],
      "metadata": {
        "id": "BjW4zUvtnOrj"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "# Let's write a training loop\n",
        "torch.manual_seed(42)\n",
        "\n",
        "epochs = 200\n",
        "\n",
        "# Put data on the target device (device agnostic code for data) \n",
        "X_train = X_train.to(device)\n",
        "y_train = y_train.to(device)\n",
        "X_test = X_test.to(device)\n",
        "y_test = y_test.to(device)\n",
        "\n",
        "for epoch in range(epochs):\n",
        "  model_1.train()\n",
        "\n",
        "  # 1. Forward pass\n",
        "  y_pred = model_1(X_train)\n",
        "\n",
        "  # 2. Calculate the loss\n",
        "  loss = loss_fn(y_pred, y_train)\n",
        "\n",
        "  # 3. Optimizer zero grad\n",
        "  optimizer.zero_grad()\n",
        "\n",
        "  # 4. Perform backpropagation\n",
        "  loss.backward()\n",
        "\n",
        "  # 5. Optimizer step\n",
        "  optimizer.step()\n",
        "\n",
        "  ### Testing\n",
        "  model_1.eval()\n",
        "  with torch.inference_mode():\n",
        "    test_pred = model_1(X_test)\n",
        "\n",
        "    test_loss = loss_fn(test_pred, y_test)\n",
        "\n",
        "  # Print out what's happening\n",
        "  if epoch % 10 == 0: \n",
        "    print(f\"Epoch: {epoch} | Loss: {loss} | Test loss: {test_loss}\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "GyAumLw3n2Hy",
        "outputId": "207e60c6-83b6-4524-da0b-ddf7d7db52e2"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Epoch: 0 | Loss: 0.5551779866218567 | Test loss: 0.5739762187004089\n",
            "Epoch: 10 | Loss: 0.439968079328537 | Test loss: 0.4392664134502411\n",
            "Epoch: 20 | Loss: 0.3247582018375397 | Test loss: 0.30455657839775085\n",
            "Epoch: 30 | Loss: 0.20954833924770355 | Test loss: 0.16984669864177704\n",
            "Epoch: 40 | Loss: 0.09433845430612564 | Test loss: 0.03513690456748009\n",
            "Epoch: 50 | Loss: 0.023886388167738914 | Test loss: 0.04784907028079033\n",
            "Epoch: 60 | Loss: 0.019956795498728752 | Test loss: 0.045803118497133255\n",
            "Epoch: 70 | Loss: 0.016517987474799156 | Test loss: 0.037530567497015\n",
            "Epoch: 80 | Loss: 0.013089174404740334 | Test loss: 0.02994490973651409\n",
            "Epoch: 90 | Loss: 0.009653178043663502 | Test loss: 0.02167237363755703\n",
            "Epoch: 100 | Loss: 0.006215683650225401 | Test loss: 0.014086711220443249\n",
            "Epoch: 110 | Loss: 0.00278724217787385 | Test loss: 0.005814164876937866\n",
            "Epoch: 120 | Loss: 0.0012645035749301314 | Test loss: 0.013801801018416882\n",
            "Epoch: 130 | Loss: 0.0012645035749301314 | Test loss: 0.013801801018416882\n",
            "Epoch: 140 | Loss: 0.0012645035749301314 | Test loss: 0.013801801018416882\n",
            "Epoch: 150 | Loss: 0.0012645035749301314 | Test loss: 0.013801801018416882\n",
            "Epoch: 160 | Loss: 0.0012645035749301314 | Test loss: 0.013801801018416882\n",
            "Epoch: 170 | Loss: 0.0012645035749301314 | Test loss: 0.013801801018416882\n",
            "Epoch: 180 | Loss: 0.0012645035749301314 | Test loss: 0.013801801018416882\n",
            "Epoch: 190 | Loss: 0.0012645035749301314 | Test loss: 0.013801801018416882\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "model_1.state_dict()"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "Q2qJjO4ko6x_",
        "outputId": "7125869b-b8f4-4ee2-ab14-571afa54c316"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "OrderedDict([('linear_layer.weight', tensor([[0.6968]], device='cuda:0')),\n",
              "             ('linear_layer.bias', tensor([0.3025], device='cuda:0'))])"
            ]
          },
          "metadata": {},
          "execution_count": 51
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "weight, bias "
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "28o1G0gnpYRj",
        "outputId": "ae9098ca-52bb-440f-84f6-7979f2ccd2c5"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "(0.7, 0.3)"
            ]
          },
          "metadata": {},
          "execution_count": 52
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 6.4 Making and evaluating predictions"
      ],
      "metadata": {
        "id": "G_Y2nC9tpaDz"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Turn model into evaluation mode\n",
        "model_1.eval()\n",
        "\n",
        "# Make predictions on the test data\n",
        "with torch.inference_mode():\n",
        "  y_preds = model_1(X_test)\n",
        "y_preds"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "Ngw4JbJQqubf",
        "outputId": "6a05ad5c-98bd-4e01-e951-a40bf84e46a0"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "tensor([[0.8600],\n",
              "        [0.8739],\n",
              "        [0.8878],\n",
              "        [0.9018],\n",
              "        [0.9157],\n",
              "        [0.9296],\n",
              "        [0.9436],\n",
              "        [0.9575],\n",
              "        [0.9714],\n",
              "        [0.9854]], device='cuda:0')"
            ]
          },
          "metadata": {},
          "execution_count": 53
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Check out our model predictions visually\n",
        "plot_predictions(predictions=y_preds.cpu())"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 428
        },
        "id": "uUIbkzHIq5U8",
        "outputId": "d1e6ead5-4ce9-42d9-c2a7-332e143408be"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlMAAAGbCAYAAADgEhWsAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAgAElEQVR4nO3de3wU9b3/8fcnCZfITZQAQhAQUcGAChGlVoOKVblIrUe5WIVqFR/Iqf6OqFRbQNTaVizHVrSoVax3RfRQpKjlgCJHJAEEhYBFUAEjBE+PF6hCyOf3x8Y0iUl2w+wtu6/n47GPzcx8Z+aTTIA3c/msubsAAABwcDISXQAAAEBjRpgCAAAIgDAFAAAQAGEKAAAgAMIUAABAAFmJ2nG7du28W7duido9AABAxFatWrXb3XNqW5awMNWtWzcVFRUlavcAAAARM7OP6lrGZT4AAIAACFMAAAABEKYAAAACIEwBAAAEQJgCAAAIIOzTfGb2iKRhkna5e14ty03SvZKGSNoraZy7rw5a2BdffKFdu3Zp//79QTeFFNekSRO1b99erVu3TnQpAIA0FElrhDmS7pP05zqWny+pZ8XrFEkPVLwftC+++EI7d+5U586dlZ2drVBeA77L3fXPf/5TO3bskCQCFQAg7sJe5nP3NyT9bz1DRkj6s4eskHSomR0RpKhdu3apc+fOOuSQQwhSqJeZ6ZBDDlHnzp21a9euRJcDAEhD0bhnqrOkbVWmt1fMO2j79+9XdnZ2oKKQXrKzs7kkDABIiLjegG5mV5tZkZkVlZaWhhsbp6qQCvh9AQAkSjTC1A5JXapM51bM+w53f9Dd8909Pyen1o+3AQAAaFSiEabmS7rcQk6V9Lm7l0RhuwAAAEkvbJgys6clvSXpWDPbbmZXmtk1ZnZNxZCFkrZI2izpIUkTYlZtGho3bpyGDRvWoHUGDRqkiRMnxqii+k2cOFGDBg1KyL4BAEiEsK0R3H10mOUu6dqoVdRIhbtnZ+zYsZozZ06Dt3vvvfcq9COO3Lx589SkSZMG7ysRPvzwQ3Xv3l2FhYXKz89PdDkAADRYJH2mEIGSkn9d2VywYIGuuuqqavNqPp24f//+iAJPmzZtGlzLYYcd1uB1AADAweHjZKKkY8eOla9DDz202ryvv/5ahx56qJ5++mmdddZZys7O1uzZs/XZZ59p9OjRys3NVXZ2to4//ng9+uij1bZb8zLfoEGDNGHCBN1yyy1q166d2rdvr0mTJqm8vLzamKqX+bp166Y77rhD48ePV+vWrZWbm6u777672n7ef/99FRQUqHnz5jr22GO1cOFCtWzZst6zaQcOHNCkSZPUtm1btW3bVtdff70OHDhQbcyiRYt0+umnq23btjrssMN07rnnqri4uHJ59+7dJUknn3yyzKzyEmFhYaF+8IMfqF27dmrdurW+//3v66233orgSAAA0snrw/qoLMP0+rA+CauBMBVHP//5zzVhwgRt2LBBP/zhD/X111+rX79+WrBggdavX6/rrrtO48eP1+LFi+vdzpNPPqmsrCz9z//8j+677z7953/+p5599tl615k5c6b69Omj1atX6+abb9ZNN91UGU7Ky8t14YUXKisrSytWrNCcOXN022236Ztvvql3m/fcc48eeughzZ49W2+99ZYOHDigJ598stqYPXv26Prrr9fKlSu1dOlStWnTRsOHD9e+ffskSStXrpQUCl0lJSWaN2+eJOnLL7/UZZddpmXLlmnlypU68cQTNWTIEH322Wf11gQASC+nLXxPWR56Txh3T8irf//+XpcNGzbUuayhJkxwz8wMvcfL888/76EfbcjWrVtdks+YMSPsuiNHjvQrr7yycnrs2LE+dOjQyumCggI/9dRTq60zePDgausUFBT4tddeWzndtWtXHzVqVLV1jj76aL/99tvd3X3RokWemZnp27dvr1y+fPlyl+SPPvponbUeccQRfscdd1ROHzhwwHv27OkFBQV1rvPVV195RkaGL1u2zN3/9bMpLCyscx139/Lycu/YsaM//vjjdY6J5u8NAKBxWDo0z/ebfOnQvJjuR1KR15FpUv7M1OzZ0oEDofdEq3mD9YEDB3TnnXeqb9++Ovzww9WyZUvNmzdPH3/8cb3b6du3b7XpTp06hf0olfrW2bhxozp16qTOnf/VuP7kk09WRkbdvx6ff/65SkpKNHDgwMp5GRkZOuWU6h/L+MEHH2jMmDHq0aOHWrdurQ4dOqi8vDzs97hr1y6NHz9exxxzjNq0aaNWrVpp165dYdcDAKSXggXvKqvcVbDg3YTVkPI3oI8fHwpS48cnuhKpRYsW1aZnzJihe+65R/fee6/69Omjli1b6pZbbgkbjGreuG5m1e6ZitY60TBs2DDl5uZq9uzZ6ty5s7KystS7d+/Ky3x1GTt2rHbu3KmZM2eqW7duatasmc4+++yw6wEAEG8pH6ZmzQq9ktGbb76p4cOH67LLLpMUuuT6/vvvV97AHi/HHXecPvnkE33yySfq1KmTJKmoqKjesNWmTRsdccQRWrFihc466yxJofpXrlypI44Ifc71Z599po0bN+r+++/XmWeeKUlavXq1ysrKKrfTtGlTSfrOjetvvvmmfv/732vo0KGSpJ07d1Z7OhIAgGSR8pf5ktkxxxyjxYsX680339TGjRs1ceJEbd26Ne51nHPOOTr22GM1duxYrV27VitWrNB//Md/KCsrq97+Wdddd51++9vfau7cudq0aZOuv/76aoGnbdu2ateunR566CFt3rxZr7/+uq655hplZf0rw7dv317Z2dl65ZVXtHPnTn3++eeSQj+bJ554Qhs2bFBhYaFGjRpVGbwAAEgmhKkE+sUvfqEBAwbo/PPP1xlnnKEWLVro0ksvjXsdGRkZevHFF/XNN99owIABGjt2rG699VaZmZo3b17nejfccIN+8pOf6Kc//alOOeUUlZeXV6s/IyNDzz77rNatW6e8vDxde+21uv3229WsWbPKMVlZWfr973+vhx9+WJ06ddKIESMkSY888oi++uor9e/fX6NGjdIVV1yhbt26xexnAABIHsnQ7qAhzBvYXTta8vPzvaioqNZlxcXF6tWrV5wrQlVr167ViSeeqKKiIvXv3z/R5USE3xsASA1lGaYsl8pMyipPTE6pycxWuXutH9XBmSlIkl588UW9+uqr2rp1q5YsWaJx48bphBNOUL9+/RJdGgAgzSwfkqcyC703Bil/Azoi8+WXX+rmm2/Wtm3b1LZtWw0aNEgzZ84M+5mDAABE27dtDgoSXEekCFOQJF1++eW6/PLLE10GAACNDpf5AAAAAiBMAQAABECYAgAAcdHYWh5EijAFAADi4rSF7ynLQ++phDAFAADiorG1PIgUT/MBAIC4aGwtDyLFmalGrFu3bpoxY0ZC9j1s2DCNGzcuIfsGACCZEKaixMzqfQUJHtOmTVNe3ndPiRYWFmrChAkBqo6fpUuXysy0e/fuRJcCAEBUcZkvSkpKSiq/XrBgga666qpq87Kzs6O+z5ycnKhvEwAANAxnpqKkY8eOla9DDz30O/PeeOMN9e/fX82bN1f37t116623at++fZXrz5s3T3379lV2drYOO+wwFRQUaOfOnZozZ45uu+02rV+/vvIs15w5cyR99zKfmenBBx/UxRdfrBYtWuioo47SE088Ua3Ot99+W/369VPz5s110kknaeHChTIzLV26tM7vbe/evRo3bpxatmypDh066Fe/+tV3xjzxxBM6+eST1apVK7Vv314XX3yxduzYIUn68MMPdeaZZ0oKBcCqZ+oWLVqk008/XW3bttVhhx2mc889V8XFxQ3++QMAEidVWx5EijAVB6+88oouvfRSTZw4UevXr9cjjzyiuXPn6pZbbpEkffrppxo1apTGjh2r4uJivfHGG7rsssskSSNHjtQNN9ygY489ViUlJSopKdHIkSPr3Nf06dM1YsQIrV27ViNHjtQVV1yhjz/+WJL01VdfadiwYTruuOO0atUq/fa3v9WNN94Ytv5Jkybptdde0wsvvKDFixdrzZo1euONN6qN2bdvn2677TatXbtWCxYs0O7duzV69GhJUpcuXfTCCy9IktavX6+SkhLde++9kqQ9e/bo+uuv18qVK7V06VK1adNGw4cPrxY0AQDJLVVbHkTM3RPy6t+/v9dlw4YNdS5rqAkLJnjmbZk+YcGEqG0znOeff95DP9qQ008/3adPn15tzIsvvugtWrTw8vJyX7VqlUvyDz/8sNbtTZ061Y8//vjvzO/atavffffdldOSfPLkyZXT+/fv9+zsbH/88cfd3f2Pf/yjt23b1vfu3Vs55sknn3RJvmTJklr3/eWXX3rTpk39iSeeqDavTZs2Pnbs2Dp/BsXFxS7Jt23b5u7uS5YscUleWlpa5zru7l999ZVnZGT4smXL6h1Xm2j+3gAAIrd0aJ7vN/nSoXmJLiVmJBV5HZkm5c9MzV41Wwf8gGavmp2wGlatWqU777xTLVu2rHyNGTNGe/bs0aeffqoTTjhBgwcPVl5eni666CI98MADKi0tPah99e3bt/LrrKws5eTkaNeuXZKkjRs3Ki8vr9r9W6ecckq92/vggw+0b98+DRw4sHJey5Yt1adP9VO5q1ev1ogRI9S1a1e1atVK+fn5klR5Vqy+7Y8ZM0Y9evRQ69at1aFDB5WXl4ddDwCQPAoWvKuscq9sfZBuUj5Mje8/XpmWqfH9xyeshvLyck2dOlXvvPNO5WvdunX6+9//rpycHGVmZurVV1/Vq6++qr59++pPf/qTevbsqbVr1zZ4X02aNKk2bWYqLy+P1rdSqz179ujcc8/VIYccoscff1yFhYVatGiRJIW9XDds2DCVlpZq9uzZevvtt7VmzRplZWVxmQ8A0Gik/NN8s4bO0qyhsxJaQ79+/bRx40YdffTRdY4xMw0cOFADBw7UlClTdPzxx+vZZ5/VCSecoKZNm+rAgQOB6zjuuOP02GOP6Z///Gfl2amVK1fWu06PHj3UpEkTrVixQkcddZSkUHh677331KNHD0mhM167d+/Wr371K3Xv3l1S6Ib6qpo2bSpJ1b6Pzz77TBs3btT9999feYP66tWrVVZWFvh7BQAgXlL+zFQymDJlip566ilNmTJF7733njZu3Ki5c+fqpptukiStWLFCd9xxhwoLC/Xxxx9r/vz52rZtm3r37i0p9NTeRx99pNWrV2v37t365ptvDqqOMWPGKDMzU1dddZU2bNigv/3tb5VP5plZreu0bNlSV155pW6++Wa99tprWr9+va644opqoejII49Us2bNdN9992nLli16+eWX9ctf/rLadrp27Soz08svv6zS0lJ99dVXatu2rdq1a6eHHnpImzdv1uuvv65rrrlGWVkpn/EBACmEMBUH5557rl5++WUtWbJEAwYM0IABA/TrX/9aRx55pCSpTZs2Wr58uYYNG6aePXvqhhtu0C9/+Uv9+Mc/liRddNFFGjJkiM4++2zl5OTo6aefPqg6WrVqpb/85S9av369TjrpJN14442aNm2aJKl58+Z1rjdjxgydeeaZuvDCC3XmmWcqLy9PZ5xxRuXynJwcPfbYY3rppZfUu3dv3Xbbbfrd735XbRudO3fWbbfdpltvvVUdOnTQxIkTlZGRoWeffVbr1q1TXl6err32Wt1+++1q1qzZQX1/AIDoSfd2Bw1hoRvU4y8/P9+LiopqXVZcXKxevXrFuaL09F//9V+68MILtWvXLrVr1y7R5QTC7w0ARE9ZhinLpTKTssoTkxWSiZmtcvf82pZxZirNPPbYY1q2bJk+/PBDLViwQNdff72GDx/e6IMUACC6lg/JU5mF3lE/bk5JMzt37tTUqVNVUlKijh07aujQofrNb36T6LIAAEnm2zYHBQmuozEgTKWZm266qfLGdwAAEByX+QAAAAIgTAEAAARAmAIAII3Q8iD6CFMAAKSR0xa+pywPvSM6CFMAAKQRWh5EH0/zAQCQRmh5EH2cmWqE5s6dW+2z9ObMmaOWLVsG2ubSpUtlZtq9e3fQ8gAASCuEqSgaN26czExmpiZNmuioo47SpEmTtGfPnpjud+TIkdqyZUvE47t166YZM2ZUm/e9731PJSUlOvzww6NdHgAAKS2iMGVm55nZJjPbbGaTa1ne1cwWm9k6M1tqZrnRL7VxGDx4sEpKSrRlyxbdcccduv/++zVp0qTvjCsrK1O0PhcxOztb7du3D7SNpk2bqmPHjtXOeAEAgPDChikzy5Q0S9L5knpLGm1mvWsMmyHpz+7eV9J0SXdFu9DGolmzZurYsaO6dOmiMWPG6NJLL9VLL72kadOmKS8vT3PmzFGPHj3UrFkz7dmzR59//rmuvvpqtW/fXq1atVJBQYFqfgD0n//8Z3Xt2lWHHHKIhg0bpp07d1ZbXttlvoULF+qUU05Rdna2Dj/8cA0fPlxff/21Bg0apI8++kg33nhj5Vk0qfbLfPPmzVOfPn3UrFkzdenSRXfeeWe1ANitWzfdcccdGj9+vFq3bq3c3Fzdfffd1eqYPXu2jjnmGDVv3lzt2rXTueeeq7Kysqj8rAEA/0LLg8SJ5MzUAEmb3X2Lu++T9IykETXG9Jb03xVfL6lledrKzs7W/v37JUlbt27VU089peeff15r165Vs2bNNHToUO3YsUMLFizQmjVrdMYZZ+iss85SSUmJJOntt9/WuHHjdPXVV+udd97R8OHDNWXKlHr3uWjRIl1wwQU655xztGrVKi1ZskQFBQUqLy/XvHnzlJubqylTpqikpKRyPzWtWrVKF198sX70ox/p3Xff1a9//Wvddddduu+++6qNmzlzpvr06aPVq1fr5ptv1k033aS33npLklRUVKRrr71WU6dO1aZNm7R48WKdd955QX+kAIBa0PIggdy93pekf5P0cJXpyyTdV2PMU5Kuq/j6R5Jc0uG1bOtqSUWSio488kivy4YNG+pc1mATJrhnZobeY2zs2LE+dOjQyum3337bDz/8cL/kkkt86tSpnpWV5Z9++mnl8sWLF3uLFi1879691bZzwgkn+G9+8xt3dx89erQPHjy42vIrr7zSQ4cu5NFHH/UWLVpUTn/ve9/zkSNH1lln165d/e677642b8mSJS7JS0tL3d19zJgxfuaZZ1YbM3XqVO/cuXO17YwaNaramKOPPtpvv/12d3d/4YUXvHXr1v7FF1/UWUs0RfX3BgAamaVD83y/yZcOzUt0KSlJUpHXkZWidQP6JEkFZrZGoactd0g6UEtwe9Dd8909PycnJ0q7DmP2bOnAgdB7HCxatEgtW7ZU8+bNNXDgQJ1xxhn6wx/+IEnKzc1Vhw4dKseuWrVKe/fuVU5Ojlq2bFn5eu+99/TBBx9IkoqLizVw4MBq+6g5XdOaNWt09tlnB/o+iouLddppp1Wb9/3vf187duzQF198UTmvb9++1cZ06tRJu3btkiSdc8456tq1q7p3765LL71Ujz32mL788stAdQEAalew4F1llXtl6wPETyR9pnZI6lJlOrdiXiV3/0ShM1Iys5aSLnL3/4tWkYGMHx8KUuPHx2V3Z5xxhh588EE1adJEnTp1UpMmTSqXtWjRotrY8vJydejQQcuWLfvOdlq3bh3zWg9W1ZvUq35/3y4rLy+XJLVq1UqrV6/WG2+8oddee0133XWXbrnlFhUWFqpTp05xrRkAgFiJ5MxUoaSeZtbdzJpKGiVpftUBZtbOzL7d1s8lPRLdMgOYNUsqKwu9x8Ehhxyio48+Wl27dv1O0KipX79+2rlzpzIyMnT00UdXe337dF6vXr20YsWKauvVnK7ppJNO0uLFi+tc3rRpUx048J0Th9X06tVLy5cvrzbvzTffVG5urlq1alXvulVlZWXprLPO0l133aV169Zpz549WrBgQcTrAwCQ7MKGKXcvkzRR0iuSiiU95+7rzWy6mV1QMWyQpE1m9r6kDpLujFG9KWXw4ME67bTTNGLECP31r3/V1q1b9dZbb2nq1KmVZ6t+9rOf6W9/+5vuuusu/f3vf9dDDz2kF198sd7t3nrrrXr++ef1i1/8Qhs2bND69es1c+ZM7d27V1LoKbxly5Zpx44ddTbpvOGGG/T6669r2rRpev/99/Xkk0/qnnvu0U033RTx97dgwQLde++9WrNmjT766CM99dRT+vLLL9WrV6+ItwEAQLKL6J4pd1/o7se4ew93v7Ni3hR3n1/x9Vx371kx5qfu/k0si04VZqaFCxfqrLPO0lVXXaVjjz1Wl1xyiTZt2lR5GezUU0/Vn/70Jz3wwAPq27ev5s2bp2nTptW73SFDhujFF1/UX//6V5100kkqKCjQkiVLlJEROtzTp0/Xtm3b1KNHD9V171q/fv30/PPP64UXXlBeXp4mT56syZMna+LEiRF/f4ceeqheeuklDR48WMcdd5xmzJihhx9+WKeffnrE2wCAdEa7g8bBPEqNIxsqPz/fa/ZT+lZxcTFnL9Bg/N4ASDVlGaYsl8pMyipPzL/XCDGzVe6eX9syPk4GAIAktXxInsos9I7kFcnTfAAAIAG+bXNQkOA6UD/OTAEAAARAmAIAAAggacPUt40fgUjw+wIASJSkDFMtWrTQjh07tG/fPiXqaUM0Du6uffv2aceOHd/pMA8AyYqWB6klKVsjlJeXa/fu3fr8889VVlYW58rQ2GRlZalNmzZq165dZS8tAEhmtDxofOprjZCUT/NlZGSoffv2lR+pAgBAKlk+JE+nLXxPy4fk8aReCkjKMAUAQCqj5UFq4ZoIAABAAIQpAACAAAhTAAAAARCmAACIEloepCfCFAAAUXLawveU5aF3pA/CFAAAUbJ8SJ7KLPSO9EFrBAAAooSWB+mJM1MAAAABEKYAAAACIEwBAAAEQJgCAKAe114rZWWF3oHaEKYAAKjH7NnSgQOhd6A2hCkAAOoxfryUmRl6B2pj7p6QHefn53tRUVFC9g0AANAQZrbK3fNrW8aZKQAAgAAIUwAAAAEQpgAAAAIgTAEA0hItDxAthCkAQFqi5QGihTAFAEhLtDxAtNAaAQAAIAxaIwAAAMQIYQoAACAAwhQAAEAAhCkAQMqg3QESgTAFAEgZtDtAIhCmAAApg3YHSARaIwAAAIRBawQAAIAYIUwBAAAEQJgCAAAIIKIwZWbnmdkmM9tsZpNrWX6kmS0xszVmts7MhkS/VABAuqLlAZJZ2BvQzSxT0vuSzpG0XVKhpNHuvqHKmAclrXH3B8yst6SF7t6tvu1yAzoAIFJZWaGWB5mZUllZoqtBOgp6A/oASZvdfYu775P0jKQRNca4pNYVX7eR9MnBFgsAQE20PEAyy4pgTGdJ26pMb5d0So0x0yS9amb/LqmFpMG1bcjMrpZ0tSQdeeSRDa0VAJCmZs0KvYBkFK0b0EdLmuPuuZKGSHrczL6zbXd/0N3z3T0/JycnSrsGAABInEjC1A5JXapM51bMq+pKSc9Jkru/Jam5pHbRKBAAACCZRRKmCiX1NLPuZtZU0ihJ82uM+VjS2ZJkZr0UClOl0SwUAAAgGYUNU+5eJmmipFckFUt6zt3Xm9l0M7ugYtgNkq4ys7WSnpY0zhP1OTUAgEaDlgdIBXw2HwAgYWh5gMaCz+YDACQlWh4gFXBmCgAAIAzOTAEAAMQIYQoAACAAwhQAAEAAhCkAQFTR7gDphjAFAIiq2bND7Q5mz050JUB8EKYAAFFFuwOkG1ojAAAAhEFrBAAAgBghTAEAAARAmAIAAAiAMAUAABAAYQoAEBH6RwG1I0wBACJC/yigdoQpAEBE6B8F1I4+UwAAAGHQZwoAACBGCFMAAAABEKYAAAACIEwBQJqj5QEQDGEKANIcLQ+AYAhTAJDmaHkABENrBAAAgDBojQAAABAjhCkAAIAACFMAAAABEKYAIAXR7gCIH8IUAKQg2h0A8UOYAoAURLsDIH5ojQAAABAGrREAAABihDAFAAAQAGEKAAAgAMIUADQitDwAkg9hCgAaEVoeAMmHMAUAjQgtD4DkQ2sEAACAMGiNAAAAECOEKQAAgAAIUwAAAAEQpgAgCdDyAGi8IgpTZnaemW0ys81mNrmW5TPN7J2K1/tm9n/RLxUAUhctD4DGK2yYMrNMSbMknS+pt6TRZta76hh3/3/ufqK7nyjpD5LmxaJYAEhVtDwAGq9IzkwNkLTZ3be4+z5Jz0gaUc/40ZKejkZxAJAuZs2SyspC7wAal0jCVGdJ26pMb6+Y9x1m1lVSd0n/Xcfyq82syMyKSktLG1orAABA0on2DeijJM119wO1LXT3B909393zc3JyorxrAACA+IskTO2Q1KXKdG7FvNqMEpf4AABAGokkTBVK6mlm3c2sqUKBaX7NQWZ2nKS2kt6KbokA0DjR7gBID2HDlLuXSZoo6RVJxZKec/f1ZjbdzC6oMnSUpGc8UR/2BwBJhnYHQHrIimSQuy+UtLDGvCk1pqdFrywAaPzGjw8FKdodAKnNEnUiKT8/34uKihKybwAAgIYws1Xunl/bMj5OBgAAIADCFAAAQACEKQAAgAAIUwDQQLQ8AFAVYQoAGoiWBwCqIkwBQAONHy9lZtLyAEAIrREAAADCoDUCAABAjBCmAAAAAiBMAQAABECYAoAKtDwAcDAIUwBQgZYHAA4GYQoAKtDyAMDBoDUCAABAGLRGAAAAiBHCFAAAQACEKQAAgAAIUwBSGu0OAMQaYQpASqPdAYBYI0wBSGm0OwAQa7RGAAAACIPWCAAAADFCmAIAAAiAMAUAABAAYQpAo0TLAwDJgjAFoFGi5QGAZEGYAtAo0fIAQLKgNQIAAEAYtEYAAACIEcIUAABAAIQpAACAAAhTAJIKLQ8ANDaEKQBJhZYHABobwhSApELLAwCNDa0RAAAAwqA1AgAAQIwQpgAAAAIgTAEAAARAmAIQc7Q7AJDKCFMAYo52BwBSWURhyszOM7NNZrbZzCbXMeYSM9tgZuvN7KnolgmgMaPdAYBUFrY1gpllSnpf0jmStksqlDTa3TdUGdNT0nOSznL3f5hZe3ffVd92aY0AAAAai6CtEQZI2uzuW6zin/cAAA0ESURBVNx9n6RnJI2oMeYqSbPc/R+SFC5IAQAApIpIwlRnSduqTG+vmFfVMZKOMbPlZrbCzM6rbUNmdrWZFZlZUWlp6cFVDAAAkESidQN6lqSekgZJGi3pITM7tOYgd3/Q3fPdPT8nJydKuwYAAEicSMLUDkldqkznVsyraruk+e6+3923KnSPVc/olAggWdHyAAAiC1OFknqaWXczaypplKT5Nca8pNBZKZlZO4Uu+22JYp0AkhAtDwAggjDl7mWSJkp6RVKxpOfcfb2ZTTezCyqGvSLpMzPbIGmJpBvd/bNYFQ0gOdDyAAAiaI0QK7RGAAAAjUXQ1ggAAACoA2EKAAAgAMIUAABAAIQpANXQ7gAAGoYwBaAa2h0AQMMQpgBUQ7sDAGgYWiMAAACEQWsEAACAGCFMAQAABECYAgAACIAwBaQJWh4AQGwQpoA0QcsDAIgNwhSQJmh5AACxQWsEAACAMGiNAAAAECOEKQAAgAAIUwAAAAEQpoBGjpYHAJBYhCmgkaPlAQAkFmEKaORoeQAAiUVrBAAAgDBojQAAABAjhCkAAIAACFMAAAABEKaAJES7AwBoPAhTQBKi3QEANB6EKSAJ0e4AABoPWiMAAACEQWsEAACAGCFMAQAABECYAgAACIAwBQAAEABhCogj+kcBQOohTAFxRP8oAEg9hCkgjugfBQCphz5TAAAAYdBnCgAAIEYIUwAAAAEQpgAAAAIgTAFRQMsDAEhfhCkgCmh5AADpizAFRAEtDwAgfUUUpszsPDPbZGabzWxyLcvHmVmpmb1T8fpp9EsFktesWVJZWegdAJBessINMLNMSbMknSNpu6RCM5vv7htqDH3W3SfGoEYAAICkFcmZqQGSNrv7FnffJ+kZSSNiWxYAAEDjEEmY6ixpW5Xp7RXzarrIzNaZ2Vwz61LbhszsajMrMrOi0tLSgygXAAAguUTrBvS/SOrm7n0lvSbpsdoGufuD7p7v7vk5OTlR2jUQG7Q7AABEIpIwtUNS1TNNuRXzKrn7Z+7+TcXkw5L6R6c8IHFodwAAiEQkYapQUk8z625mTSWNkjS/6gAzO6LK5AWSiqNXIpAYtDsAAEQi7NN87l5mZhMlvSIpU9Ij7r7ezKZLKnL3+ZJ+ZmYXSCqT9L+SxsWwZiAuZs2i1QEAIDxz94TsOD8/34uKihKybwAAgIYws1Xunl/bMjqgAwAABECYAgAACIAwhbRDywMAQDQRppB2aHkAAIgmwhTSDi0PAADRxNN8AAAAYfA0HwAAQIwQpgAAAAIgTAEAAARAmELKoOUBACARCFNIGbQ8AAAkAmEKKYOWBwCARKA1AgAAQBi0RgAAAIgRwhQAAEAAhCkAAIAACFNIarQ7AAAkO8IUkhrtDgAAyY4whaRGuwMAQLKjNQIAAEAYtEYAAACIEcIUAABAAIQpAACAAAhTSAhaHgAAUgVhCglBywMAQKogTCEhaHkAAEgVtEYAAAAIg9YIAAAAMUKYAgAACIAwBQAAEABhClFFywMAQLohTCGqaHkAAEg3hClEFS0PAADphtYIAAAAYdAaAQAAIEYIUwAAAAEQpgAAAAIgTCEs2h0AAFA3whTCot0BAAB1I0whLNodAABQN1ojAAAAhBG4NYKZnWdmm8xss5lNrmfcRWbmZlbrzgAAAFJN2DBlZpmSZkk6X1JvSaPNrHct41pJuk7S29EuEgAAIFlFcmZqgKTN7r7F3fdJekbSiFrG3S7pN5K+jmJ9AAAASS2SMNVZ0rYq09sr5lUys36Surj7y/VtyMyuNrMiMysqLS1tcLGILloeAAAQXOCn+cwsQ9LvJN0Qbqy7P+ju+e6en5OTE3TXCIiWBwAABBdJmNohqUuV6dyKed9qJSlP0lIz+1DSqZLmcxN68qPlAQAAwYVtjWBmWZLel3S2QiGqUNIYd19fx/ilkia5e719D2iNAAAAGotArRHcvUzSREmvSCqW9Jy7rzez6WZ2QXRLBQAAaFyyIhnk7gslLawxb0odYwcFLwsAAKBx4ONkAAAAAiBMpSBaHgAAED+EqRREywMAAOKHMJWCaHkAAED8hG2NECu0RgAAAI1FoNYIAAAAqBthCgAAIADCFAAAQACEqUaCdgcAACQnwlQjQbsDAACSE2GqkaDdAQAAyYnWCAAAAGHQGgEAACBGCFMAAAABEKYAAAACIEwlGC0PAABo3AhTCUbLAwAAGjfCVILR8gAAgMaN1ggAAABh0BoBAAAgRghTAAAAARCmAAAAAiBMxQDtDgAASB+EqRig3QEAAOmDMBUDtDsAACB90BoBAAAgDFojAAAAxAhhCgAAIADCFAAAQACEqQag5QEAAKiJMNUAtDwAAAA1EaYagJYHAACgJlojAAAAhEFrBAAAgBghTAEAAARAmAIAAAiAMCVaHgAAgINHmBItDwAAwMEjTImWBwAA4ODRGgEAACAMWiMAAADESERhyszOM7NNZrbZzCbXsvwaM3vXzN4xszfNrHf0SwUAAEg+YcOUmWVKmiXpfEm9JY2uJSw95e593P1ESb+V9LuoVwoAAJCEIjkzNUDSZnff4u77JD0jaUTVAe7+RZXJFpIScyMWAABAnEUSpjpL2lZlenvFvGrM7Foz+0ChM1M/i055B4/eUQAAIB6idgO6u89y9x6Sbpb0i9rGmNnVZlZkZkWlpaXR2nWt6B0FAADiIZIwtUNSlyrTuRXz6vKMpB/WtsDdH3T3fHfPz8nJibzKg0DvKAAAEA+RhKlCST3NrLuZNZU0StL8qgPMrGeVyaGS/h69Eg/OrFlSWVnoHQAAIFaywg1w9zIzmyjpFUmZkh5x9/VmNl1SkbvPlzTRzAZL2i/pH5LGxrJoAACAZBE2TEmSuy+UtLDGvClVvr4uynUBAAA0CnRABwAACIAwBQAAEABhCgAAIADCFAAAQACEKQAAgAAIUwAAAAEQpgAAAAIgTAEAAARAmAIAAAiAMAUAABAAYQoAACAAwhQAAEAA5u6J2bFZqaSPYrybdpJ2x3gfOHgcn+TFsUluHJ/kxvFJXkGOTVd3z6ltQcLCVDyYWZG75ye6DtSO45O8ODbJjeOT3Dg+yStWx4bLfAAAAAEQpgAAAAJI9TD1YKILQL04PsmLY5PcOD7JjeOTvGJybFL6nikAAIBYS/UzUwAAADFFmAIAAAggJcKUmZ1nZpvMbLOZTa5leTMze7Zi+dtm1i3+VaavCI7Pf5jZBjNbZ2aLzaxrIupMR+GOTZVxF5mZmxmPe8dRJMfHzC6p+POz3syeineN6SqCv9eONLMlZram4u+2IYmoMx2Z2SNmtsvM3qtjuZnZ7yuO3Toz6xd0n40+TJlZpqRZks6X1FvSaDPrXWPYlZL+4e5HS5op6TfxrTJ9RXh81kjKd/e+kuZK+m18q0xPER4bmVkrSddJeju+Faa3SI6PmfWU9HNJp7n78ZKuj3uhaSjCPzu/kPScu58kaZSk++NbZVqbI+m8epafL6lnxetqSQ8E3WGjD1OSBkja7O5b3H2fpGckjagxZoSkxyq+nivpbDOzONaYzsIeH3df4u57KyZXSMqNc43pKpI/O5J0u0L/Afk6nsUhouNzlaRZ7v4PSXL3XXGuMV1FcmxcUuuKr9tI+iSO9aU1d39D0v/WM2SEpD97yApJh5rZEUH2mQphqrOkbVWmt1fMq3WMu5dJ+lzS4XGpDpEcn6qulPTXmFaEb4U9NhWnv7u4+8vxLAySIvuzc4ykY8xsuZmtMLP6/jeO6Ink2EyT9GMz2y5poaR/j09piEBD/10KKytQOUAUmdmPJeVLKkh0LZDMLEPS7ySNS3ApqFuWQpcqBil0RvcNM+vj7v+X0KogSaMlzXH3e8xsoKTHzSzP3csTXRiiLxXOTO2Q1KXKdG7FvFrHmFmWQqdcP4tLdYjk+MjMBku6VdIF7v5NnGpLd+GOTStJeZKWmtmHkk6VNJ+b0OMmkj872yXNd/f97r5V0vsKhSvEViTH5kpJz0mSu78lqblCH7KLxIvo36WGSIUwVSipp5l1N7OmCt3oN7/GmPmSxlZ8/W+S/tvpVhovYY+PmZ0kabZCQYp7PuKn3mPj7p+7ezt37+bu3RS6n+0Cdy9KTLlpJ5K/215S6KyUzKydQpf9tsSzyDQVybH5WNLZkmRmvRQKU6VxrRJ1mS/p8oqn+k6V9Lm7lwTZYKO/zOfuZWY2UdIrkjIlPeLu681suqQid58v6U8KnWLdrNBNaaMSV3F6ifD43C2ppaTnK54L+NjdL0hY0WkiwmODBInw+Lwi6QdmtkHSAUk3ujtn3WMswmNzg6SHzOz/KXQz+jj+Ex8fZva0Qv/JaFdxz9pUSU0kyd3/qNA9bEMkbZa0V9JPAu+TYwsAAHDwUuEyHwAAQMIQpgAAAAIgTAEAAARAmAIAAAiAMAUAABAAYQoAACAAwhQAAEAA/x9eclH/ZfInFQAAAABJRU5ErkJggg==\n",
            "text/plain": [
              "<Figure size 720x504 with 1 Axes>"
            ]
          },
          "metadata": {
            "needs_background": "light"
          }
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### 6.5 Saving & loading a trained model"
      ],
      "metadata": {
        "id": "1qdw43z6rEZB"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from pathlib import Path\n",
        "\n",
        "# 1. Create models directory\n",
        "MODEL_PATH = Path(\"models\")\n",
        "MODEL_PATH.mkdir(parents=True, exist_ok=True)\n",
        "\n",
        "# 2. Create model save path\n",
        "MODEL_NAME = \"01_pytorch_workflow_model_1.pth\"\n",
        "MODEL_SAVE_PATH = MODEL_PATH / MODEL_NAME\n",
        "\n",
        "# 3. Save the model state dict\n",
        "print(f\"Saving model to: {MODEL_SAVE_PATH}\")\n",
        "torch.save(obj=model_1.state_dict(),\n",
        "           f=MODEL_SAVE_PATH) "
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "YkPtfsseriP_",
        "outputId": "5cc74c45-2adc-43d5-806c-b1446c199eee"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Saving model to: models/01_pytorch_workflow_model_1.pth\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "model_1.state_dict()"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "G5n_xTjssNZ2",
        "outputId": "14c63c26-3ae1-458c-d172-2ba03e99479d"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "OrderedDict([('linear_layer.weight', tensor([[0.6968]], device='cuda:0')),\n",
              "             ('linear_layer.bias', tensor([0.3025], device='cuda:0'))])"
            ]
          },
          "metadata": {},
          "execution_count": 58
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Load a PyTorch model\n",
        "\n",
        "# Create a new instance of lienar regression model V2\n",
        "loaded_model_1 = LinearRegressionModelV2()\n",
        "\n",
        "# Load the saved model_1 state_dict\n",
        "loaded_model_1.load_state_dict(torch.load(MODEL_SAVE_PATH))\n",
        "\n",
        "# Put the loaded model to device\n",
        "loaded_model_1.to(device)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "yj8GttgnsNXq",
        "outputId": "d934cb26-e89a-4344-f904-26d6cb06487c"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "LinearRegressionModelV2(\n",
              "  (linear_layer): Linear(in_features=1, out_features=1, bias=True)\n",
              ")"
            ]
          },
          "metadata": {},
          "execution_count": 60
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "next(loaded_model_1.parameters()).device"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "4pSqRcShsNVE",
        "outputId": "fde1b8e2-cab1-45e8-e8d4-2b2255a2b9d7"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "device(type='cuda', index=0)"
            ]
          },
          "metadata": {},
          "execution_count": 61
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "loaded_model_1.state_dict()"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "S1nhEK4FsNSy",
        "outputId": "85d3f5b0-52b8-444f-c1cf-f2d2752cc78c"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "OrderedDict([('linear_layer.weight', tensor([[0.6968]], device='cuda:0')),\n",
              "             ('linear_layer.bias', tensor([0.3025], device='cuda:0'))])"
            ]
          },
          "metadata": {},
          "execution_count": 62
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Evaluate loaded model\n",
        "loaded_model_1.eval()\n",
        "with torch.inference_mode():\n",
        "  loaded_model_1_preds = loaded_model_1(X_test)\n",
        "y_preds == loaded_model_1_preds"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "aOZBAa-JsNQJ",
        "outputId": "9b722c03-be13-44a8-934d-33613490bb4d"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "tensor([[True],\n",
              "        [True],\n",
              "        [True],\n",
              "        [True],\n",
              "        [True],\n",
              "        [True],\n",
              "        [True],\n",
              "        [True],\n",
              "        [True],\n",
              "        [True]], device='cuda:0')"
            ]
          },
          "metadata": {},
          "execution_count": 63
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Exercises & Extra-curriculum\n",
        "\n",
        "For exercise & extra-curriculum, refer to: https://www.learnpytorch.io/01_pytorch_workflow/#exercises "
      ],
      "metadata": {
        "id": "eh1HuUNmtxt_"
      }
    },
    {
      "cell_type": "code",
      "source": [
        ""
      ],
      "metadata": {
        "id": "5DNZm0YkvEWZ"
      },
      "execution_count": null,
      "outputs": []
    }
  ]
}
